Skip to main content

solana_core/consensus/
progress_map.rs

1use {
2    crate::{
3        cluster_info_vote_listener::SlotVoteTracker,
4        cluster_slots_service::slot_supporters::SlotSupporters,
5        consensus::{Stake, ThresholdDecision, VotedStakes},
6        replay_stage::SUPERMINORITY_THRESHOLD,
7    },
8    solana_clock::Slot,
9    solana_hash::Hash,
10    solana_ledger::blockstore_processor::{
11        AsyncVerificationProgress, ConfirmationProgress, ReplaySlotStats,
12    },
13    solana_pubkey::Pubkey,
14    solana_runtime::{bank::Bank, bank_forks::BankForks},
15    solana_vote::vote_account::VoteAccountsHashMap,
16    std::{
17        collections::{HashMap, HashSet},
18        sync::{Arc, RwLock},
19        time::Instant,
20    },
21};
22
23type VotedSlot = Slot;
24type ExpirationSlot = Slot;
25
26#[derive(Clone, Copy, Debug)]
27pub struct LockoutInterval {
28    pub voter: Pubkey,
29    pub start: VotedSlot,
30    pub end: ExpirationSlot,
31}
32
33pub type LockoutIntervals = Vec<LockoutInterval>;
34
35#[derive(Debug)]
36pub struct ValidatorStakeInfo {
37    pub validator_vote_pubkey: Pubkey,
38    pub stake: u64,
39    pub total_epoch_stake: u64,
40}
41
42impl Default for ValidatorStakeInfo {
43    fn default() -> Self {
44        Self {
45            stake: 0,
46            validator_vote_pubkey: Pubkey::default(),
47            total_epoch_stake: 1,
48        }
49    }
50}
51
52impl ValidatorStakeInfo {
53    pub fn new(validator_vote_pubkey: Pubkey, stake: u64, total_epoch_stake: u64) -> Self {
54        Self {
55            validator_vote_pubkey,
56            stake,
57            total_epoch_stake,
58        }
59    }
60}
61
62pub const RETRANSMIT_BASE_DELAY_MS: u64 = 5_000;
63pub const RETRANSMIT_BACKOFF_CAP: u32 = 6;
64
65#[derive(Debug)]
66pub struct RetransmitInfo {
67    pub(crate) retry_time: Instant,
68    pub(crate) retry_iteration: u32,
69}
70
71impl RetransmitInfo {
72    pub fn reached_retransmit_threshold(&self) -> bool {
73        let backoff = std::cmp::min(self.retry_iteration, RETRANSMIT_BACKOFF_CAP);
74        let backoff_duration_ms = (1_u64 << backoff) * RETRANSMIT_BASE_DELAY_MS;
75        self.retry_time.elapsed().as_millis() > u128::from(backoff_duration_ms)
76    }
77
78    pub fn increment_retry_iteration(&mut self) {
79        self.retry_iteration = self.retry_iteration.saturating_add(1);
80        self.retry_time = Instant::now();
81    }
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
85pub enum DeadSlotReason {
86    /// Dead and cannot be brought back to life by an `UpdateParent` marker.
87    Hard,
88    /// Replay execution failed, but an `UpdateParent` marker could still make
89    /// the failed prefix obsolete.
90    ReplayFailureBeforeUpdateParent,
91}
92
93pub struct ForkProgress {
94    pub dead_reason: Option<DeadSlotReason>,
95    pub fork_stats: ForkStats,
96    pub propagated_stats: PropagatedStats,
97    pub replay_stats: Arc<RwLock<ReplaySlotStats>>,
98    pub replay_progress: Arc<RwLock<ConfirmationProgress>>,
99    pub retransmit_info: RetransmitInfo,
100    // Note `num_blocks_on_fork` and `num_dropped_blocks_on_fork` only
101    // count new blocks replayed since last restart, which won't include
102    // blocks already existing in the ledger/before snapshot at start,
103    // so these stats do not span all of time
104    pub num_blocks_on_fork: u64,
105    pub num_dropped_blocks_on_fork: u64,
106}
107
108impl ForkProgress {
109    pub fn new(
110        last_entry: Hash,
111        prev_leader_slot: Option<Slot>,
112        validator_stake_info: Option<ValidatorStakeInfo>,
113        num_blocks_on_fork: u64,
114        num_dropped_blocks_on_fork: u64,
115        async_verification: Option<AsyncVerificationProgress>,
116    ) -> Self {
117        let (
118            is_leader_slot,
119            propagated_validators_stake,
120            propagated_validators,
121            is_propagated,
122            total_epoch_stake,
123        ) = validator_stake_info
124            .map(|info| {
125                (
126                    true,
127                    info.stake,
128                    vec![info.validator_vote_pubkey].into_iter().collect(),
129                    {
130                        if info.total_epoch_stake == 0 {
131                            true
132                        } else {
133                            info.stake as f64 / info.total_epoch_stake as f64
134                                > SUPERMINORITY_THRESHOLD
135                        }
136                    },
137                    info.total_epoch_stake,
138                )
139            })
140            .unwrap_or((false, 0, HashSet::new(), false, 0));
141
142        Self {
143            dead_reason: None,
144            fork_stats: ForkStats::default(),
145            replay_stats: Arc::new(RwLock::new(ReplaySlotStats::default())),
146            replay_progress: Arc::new(RwLock::new(
147                ConfirmationProgress::new_with_async_verification(last_entry, async_verification),
148            )),
149            num_blocks_on_fork,
150            num_dropped_blocks_on_fork,
151            propagated_stats: PropagatedStats {
152                propagated_validators,
153                propagated_validators_stake,
154                is_propagated,
155                is_leader_slot,
156                prev_leader_slot,
157                total_epoch_stake,
158                ..PropagatedStats::default()
159            },
160            retransmit_info: RetransmitInfo {
161                retry_time: Instant::now(),
162                retry_iteration: 0u32,
163            },
164        }
165    }
166
167    pub fn new_from_bank(
168        bank: &Bank,
169        validator_identity: &Pubkey,
170        validator_vote_pubkey: &Pubkey,
171        prev_leader_slot: Option<Slot>,
172        num_blocks_on_fork: u64,
173        num_dropped_blocks_on_fork: u64,
174        async_verification: Option<AsyncVerificationProgress>,
175    ) -> Self {
176        let validator_stake_info = {
177            if bank.leader_id() == validator_identity {
178                Some(ValidatorStakeInfo::new(
179                    *validator_vote_pubkey,
180                    bank.epoch_vote_account_stake(validator_vote_pubkey),
181                    bank.total_epoch_stake(),
182                ))
183            } else {
184                None
185            }
186        };
187
188        let mut new_progress = Self::new(
189            bank.last_blockhash(),
190            prev_leader_slot,
191            validator_stake_info,
192            num_blocks_on_fork,
193            num_dropped_blocks_on_fork,
194            async_verification,
195        );
196
197        if bank.is_frozen() {
198            new_progress.fork_stats.bank_hash = Some(bank.hash());
199        }
200        new_progress
201    }
202
203    pub fn mark_dead(&mut self, reason: DeadSlotReason) {
204        self.dead_reason = Some(reason);
205    }
206}
207
208#[derive(Debug, Clone, Default)]
209pub struct ForkStats {
210    pub fork_stake: Stake,
211    pub total_stake: Stake,
212    pub block_height: u64,
213    pub has_voted: bool,
214    pub is_recent: bool,
215    pub is_empty: bool,
216    pub vote_threshold: Vec<ThresholdDecision>,
217    pub is_locked_out: bool,
218    pub voted_stakes: VotedStakes,
219    pub duplicate_confirmed_hash: Option<Hash>,
220    pub computed: bool,
221    pub lockout_intervals: LockoutIntervals,
222    pub bank_hash: Option<Hash>,
223    pub my_latest_landed_vote: Option<Slot>,
224}
225
226impl ForkStats {
227    /// Return fork_weight, i.e. bank_stake over total_stake.
228    pub fn fork_weight(&self) -> f64 {
229        self.fork_stake as f64 / self.total_stake as f64
230    }
231}
232
233#[derive(Clone, Default)]
234pub struct PropagatedStats {
235    pub propagated_validators: HashSet<Pubkey>,
236    pub propagated_node_ids: HashSet<Pubkey>,
237    pub propagated_validators_stake: u64,
238    pub is_propagated: bool,
239    pub is_leader_slot: bool,
240    pub prev_leader_slot: Option<Slot>,
241    pub slot_vote_tracker: Option<Arc<RwLock<SlotVoteTracker>>>,
242    pub cluster_slot_pubkeys: Option<Arc<SlotSupporters>>,
243    pub total_epoch_stake: u64,
244}
245
246impl PropagatedStats {
247    pub fn add_vote_pubkey(&mut self, vote_pubkey: Pubkey, stake: u64) {
248        if self.propagated_validators.insert(vote_pubkey) {
249            self.propagated_validators_stake += stake;
250        }
251    }
252
253    pub fn add_node_pubkey(&mut self, node_pubkey: &Pubkey, bank: &Bank) {
254        if !self.propagated_node_ids.contains(node_pubkey) {
255            let node_vote_accounts = bank
256                .epoch_vote_accounts_for_node_id(node_pubkey)
257                .map(|v| &v.vote_accounts);
258
259            if let Some(node_vote_accounts) = node_vote_accounts {
260                self.add_node_pubkey_internal(
261                    node_pubkey,
262                    node_vote_accounts,
263                    bank.epoch_vote_accounts(bank.epoch())
264                        .expect("Epoch stakes for bank's own epoch must exist"),
265                );
266            }
267        }
268    }
269
270    fn add_node_pubkey_internal(
271        &mut self,
272        node_pubkey: &Pubkey,
273        vote_account_pubkeys: &[Pubkey],
274        epoch_vote_accounts: &VoteAccountsHashMap,
275    ) {
276        self.propagated_node_ids.insert(*node_pubkey);
277        for vote_account_pubkey in vote_account_pubkeys.iter() {
278            let stake = epoch_vote_accounts
279                .get(vote_account_pubkey)
280                .map(|(stake, _)| *stake)
281                .unwrap_or(0);
282            self.add_vote_pubkey(*vote_account_pubkey, stake);
283        }
284    }
285}
286
287#[derive(Default)]
288pub struct ProgressMap {
289    progress_map: HashMap<Slot, ForkProgress>,
290    /// Tracks the number of times a slot was switched from an alternate location.
291    /// This persists even if the slot is removed from the progress_map due to a switch.
292    bank_switch_counts: HashMap<Slot, u64>,
293}
294
295impl std::ops::Deref for ProgressMap {
296    type Target = HashMap<Slot, ForkProgress>;
297    fn deref(&self) -> &Self::Target {
298        &self.progress_map
299    }
300}
301
302impl std::ops::DerefMut for ProgressMap {
303    fn deref_mut(&mut self) -> &mut Self::Target {
304        &mut self.progress_map
305    }
306}
307
308impl ProgressMap {
309    pub fn insert(&mut self, slot: Slot, fork_progress: ForkProgress) {
310        let num_bank_switches = self.get_num_bank_switches(slot);
311        fork_progress
312            .replay_stats
313            .write()
314            .unwrap()
315            .num_bank_switches = num_bank_switches;
316        self.progress_map.insert(slot, fork_progress);
317    }
318
319    pub fn get_propagated_stats(&self, slot: Slot) -> Option<&PropagatedStats> {
320        self.progress_map
321            .get(&slot)
322            .map(|fork_progress| &fork_progress.propagated_stats)
323    }
324
325    pub fn get_propagated_stats_mut(&mut self, slot: Slot) -> Option<&mut PropagatedStats> {
326        self.progress_map
327            .get_mut(&slot)
328            .map(|fork_progress| &mut fork_progress.propagated_stats)
329    }
330
331    pub fn get_propagated_stats_must_exist(&self, slot: Slot) -> &PropagatedStats {
332        self.get_propagated_stats(slot)
333            .unwrap_or_else(|| panic!("slot={slot} must exist in ProgressMap"))
334    }
335
336    pub fn get_fork_stats(&self, slot: Slot) -> Option<&ForkStats> {
337        self.progress_map
338            .get(&slot)
339            .map(|fork_progress| &fork_progress.fork_stats)
340    }
341
342    pub fn increment_num_bank_switches(&mut self, slot: Slot) {
343        let count = self.bank_switch_counts.entry(slot).or_insert(0);
344        *count = count.saturating_add(1);
345    }
346
347    pub fn get_num_bank_switches(&self, slot: Slot) -> u64 {
348        self.bank_switch_counts.get(&slot).cloned().unwrap_or(0)
349    }
350
351    pub fn get_fork_stats_mut(&mut self, slot: Slot) -> Option<&mut ForkStats> {
352        self.progress_map
353            .get_mut(&slot)
354            .map(|fork_progress| &mut fork_progress.fork_stats)
355    }
356
357    pub fn get_retransmit_info(&self, slot: Slot) -> Option<&RetransmitInfo> {
358        self.progress_map
359            .get(&slot)
360            .map(|fork_progress| &fork_progress.retransmit_info)
361    }
362
363    pub fn get_retransmit_info_mut(&mut self, slot: Slot) -> Option<&mut RetransmitInfo> {
364        self.progress_map
365            .get_mut(&slot)
366            .map(|fork_progress| &mut fork_progress.retransmit_info)
367    }
368
369    pub fn is_dead(&self, slot: Slot) -> Option<bool> {
370        self.progress_map
371            .get(&slot)
372            .map(|fork_progress| fork_progress.dead_reason.is_some())
373    }
374
375    pub fn dead_reason(&self, slot: Slot) -> Option<&DeadSlotReason> {
376        self.progress_map
377            .get(&slot)
378            .and_then(|fork_progress| fork_progress.dead_reason.as_ref())
379    }
380
381    pub fn get_hash(&self, slot: Slot) -> Option<Hash> {
382        self.progress_map
383            .get(&slot)
384            .and_then(|fork_progress| fork_progress.fork_stats.bank_hash)
385    }
386
387    pub fn is_propagated(&self, slot: Slot) -> Option<bool> {
388        self.get_propagated_stats(slot)
389            .map(|stats| stats.is_propagated)
390    }
391
392    pub fn get_latest_leader_slot_must_exist(&self, slot: Slot) -> Option<Slot> {
393        let propagated_stats = self.get_propagated_stats_must_exist(slot);
394        if propagated_stats.is_leader_slot {
395            Some(slot)
396        } else {
397            propagated_stats.prev_leader_slot
398        }
399    }
400
401    pub fn get_leader_propagation_slot_must_exist(&self, slot: Slot) -> (bool, Option<Slot>) {
402        if let Some(leader_slot) = self.get_latest_leader_slot_must_exist(slot) {
403            // If the leader's stats are None (isn't in the
404            // progress map), this means that prev_leader slot is
405            // rooted, so return true
406            (
407                self.is_propagated(leader_slot).unwrap_or(true),
408                Some(leader_slot),
409            )
410        } else {
411            // prev_leader_slot doesn't exist because already rooted
412            // or this validator hasn't been scheduled as a leader
413            // yet. In both cases the latest leader is vacuously
414            // confirmed
415            (true, None)
416        }
417    }
418
419    pub fn my_latest_landed_vote(&self, slot: Slot) -> Option<Slot> {
420        self.progress_map
421            .get(&slot)
422            .and_then(|s| s.fork_stats.my_latest_landed_vote)
423    }
424
425    pub fn set_duplicate_confirmed_hash(&mut self, slot: Slot, hash: Hash) {
426        let slot_progress = self.get_mut(&slot).unwrap();
427        slot_progress.fork_stats.duplicate_confirmed_hash = Some(hash);
428    }
429
430    pub fn is_duplicate_confirmed(&self, slot: Slot) -> Option<bool> {
431        self.progress_map
432            .get(&slot)
433            .map(|s| s.fork_stats.duplicate_confirmed_hash.is_some())
434    }
435
436    pub fn get_bank_prev_leader_slot(&self, bank: &Bank) -> Option<Slot> {
437        let parent_slot = bank.parent_slot();
438        self.get_propagated_stats(parent_slot)
439            .map(|stats| {
440                if stats.is_leader_slot {
441                    Some(parent_slot)
442                } else {
443                    stats.prev_leader_slot
444                }
445            })
446            .unwrap_or(None)
447    }
448
449    pub fn handle_new_root(&mut self, bank_forks: &BankForks) {
450        self.progress_map
451            .retain(|k, _| bank_forks.get(*k).is_some());
452        self.bank_switch_counts
453            .retain(|k, _| bank_forks.get(*k).is_some());
454    }
455
456    pub fn log_propagated_stats(&self, slot: Slot, bank_forks: &RwLock<BankForks>) {
457        if let Some(stats) = self.get_propagated_stats(slot) {
458            info!(
459                "Propagated stats: total staked: {}, observed staked: {}, vote pubkeys: {:?}, \
460                 node_pubkeys: {:?}, slot: {slot}, epoch: {:?}",
461                stats.total_epoch_stake,
462                stats.propagated_validators_stake,
463                stats.propagated_validators,
464                stats.propagated_node_ids,
465                bank_forks.read().unwrap().get(slot).map(|x| x.epoch()),
466            );
467        }
468    }
469}
470
471#[cfg(test)]
472mod test {
473    use {super::*, solana_vote::vote_account::VoteAccount};
474
475    #[test]
476    fn test_add_vote_pubkey() {
477        let mut stats = PropagatedStats::default();
478        let mut vote_pubkey = solana_pubkey::new_rand();
479
480        // Add a vote pubkey, the number of references in all_pubkeys
481        // should be 2
482        stats.add_vote_pubkey(vote_pubkey, 1);
483        assert!(stats.propagated_validators.contains(&vote_pubkey));
484        assert_eq!(stats.propagated_validators_stake, 1);
485
486        // Adding it again should change no state since the key already existed
487        stats.add_vote_pubkey(vote_pubkey, 1);
488        assert!(stats.propagated_validators.contains(&vote_pubkey));
489        assert_eq!(stats.propagated_validators_stake, 1);
490
491        // Adding another pubkey should succeed
492        vote_pubkey = solana_pubkey::new_rand();
493        stats.add_vote_pubkey(vote_pubkey, 2);
494        assert!(stats.propagated_validators.contains(&vote_pubkey));
495        assert_eq!(stats.propagated_validators_stake, 3);
496    }
497
498    #[test]
499    fn test_add_node_pubkey_internal() {
500        let num_vote_accounts = 10;
501        let staked_vote_accounts = 5;
502        let vote_account_pubkeys: Vec<_> = std::iter::repeat_with(solana_pubkey::new_rand)
503            .take(num_vote_accounts)
504            .collect();
505        let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
506            .iter()
507            .skip(num_vote_accounts - staked_vote_accounts)
508            .map(|pubkey| (*pubkey, (1, VoteAccount::new_random())))
509            .collect();
510
511        let mut stats = PropagatedStats::default();
512        let mut node_pubkey = solana_pubkey::new_rand();
513
514        // Add a vote pubkey, the number of references in all_pubkeys
515        // should be 2
516        stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
517        assert!(stats.propagated_node_ids.contains(&node_pubkey));
518        assert_eq!(
519            stats.propagated_validators_stake,
520            staked_vote_accounts as u64
521        );
522
523        // Adding it again should not change any state
524        stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
525        assert!(stats.propagated_node_ids.contains(&node_pubkey));
526        assert_eq!(
527            stats.propagated_validators_stake,
528            staked_vote_accounts as u64
529        );
530
531        // Adding another pubkey with same vote accounts should succeed, but stake
532        // shouldn't increase
533        node_pubkey = solana_pubkey::new_rand();
534        stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
535        assert!(stats.propagated_node_ids.contains(&node_pubkey));
536        assert_eq!(
537            stats.propagated_validators_stake,
538            staked_vote_accounts as u64
539        );
540
541        // Adding another pubkey with different vote accounts should succeed
542        // and increase stake
543        node_pubkey = solana_pubkey::new_rand();
544        let vote_account_pubkeys: Vec<_> = std::iter::repeat_with(solana_pubkey::new_rand)
545            .take(num_vote_accounts)
546            .collect();
547        let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
548            .iter()
549            .skip(num_vote_accounts - staked_vote_accounts)
550            .map(|pubkey| (*pubkey, (1, VoteAccount::new_random())))
551            .collect();
552        stats.add_node_pubkey_internal(&node_pubkey, &vote_account_pubkeys, &epoch_vote_accounts);
553        assert!(stats.propagated_node_ids.contains(&node_pubkey));
554        assert_eq!(
555            stats.propagated_validators_stake,
556            2 * staked_vote_accounts as u64
557        );
558    }
559
560    #[test]
561    fn test_is_propagated_status_on_construction() {
562        // If the given ValidatorStakeInfo == None, then this is not
563        // a leader slot and is_propagated == false
564        let progress = ForkProgress::new(Hash::default(), Some(9), None, 0, 0, None);
565        assert!(!progress.propagated_stats.is_propagated);
566
567        // If the stake is zero, then threshold is always achieved
568        let progress = ForkProgress::new(
569            Hash::default(),
570            Some(9),
571            Some(ValidatorStakeInfo {
572                total_epoch_stake: 0,
573                ..ValidatorStakeInfo::default()
574            }),
575            0,
576            0,
577            None,
578        );
579        assert!(progress.propagated_stats.is_propagated);
580
581        // If the stake is non zero, then threshold is not achieved unless
582        // validator has enough stake by itself to pass threshold
583        let progress = ForkProgress::new(
584            Hash::default(),
585            Some(9),
586            Some(ValidatorStakeInfo {
587                total_epoch_stake: 2,
588                ..ValidatorStakeInfo::default()
589            }),
590            0,
591            0,
592            None,
593        );
594        assert!(!progress.propagated_stats.is_propagated);
595
596        // Give the validator enough stake by itself to pass threshold
597        let progress = ForkProgress::new(
598            Hash::default(),
599            Some(9),
600            Some(ValidatorStakeInfo {
601                stake: 1,
602                total_epoch_stake: 2,
603                ..ValidatorStakeInfo::default()
604            }),
605            0,
606            0,
607            None,
608        );
609        assert!(progress.propagated_stats.is_propagated);
610
611        // Check that the default ValidatorStakeInfo::default() constructs a ForkProgress
612        // with is_propagated == false, otherwise propagation tests will fail to run
613        // the proper checks (most will auto-pass without checking anything)
614        let progress = ForkProgress::new(
615            Hash::default(),
616            Some(9),
617            Some(ValidatorStakeInfo::default()),
618            0,
619            0,
620            None,
621        );
622        assert!(!progress.propagated_stats.is_propagated);
623    }
624
625    #[test]
626    fn test_is_propagated() {
627        let mut progress_map = ProgressMap::default();
628
629        // Insert new ForkProgress for slot 10 (not a leader slot) and its
630        // previous leader slot 9 (leader slot)
631        progress_map.insert(
632            10,
633            ForkProgress::new(Hash::default(), Some(9), None, 0, 0, None),
634        );
635        progress_map.insert(
636            9,
637            ForkProgress::new(
638                Hash::default(),
639                None,
640                Some(ValidatorStakeInfo::default()),
641                0,
642                0,
643                None,
644            ),
645        );
646
647        // None of these slot have parents which are confirmed
648        assert!(!progress_map.get_leader_propagation_slot_must_exist(9).0);
649        assert!(!progress_map.get_leader_propagation_slot_must_exist(10).0);
650
651        // Insert new ForkProgress for slot 8 with no previous leader.
652        // The previous leader before 8, slot 7, does not exist in
653        // progress map, so is_propagated(8) should return true as
654        // this implies the parent is rooted
655        progress_map.insert(
656            8,
657            ForkProgress::new(Hash::default(), Some(7), None, 0, 0, None),
658        );
659        assert!(progress_map.get_leader_propagation_slot_must_exist(8).0);
660
661        // If we set the is_propagated = true, is_propagated should return true
662        progress_map
663            .get_propagated_stats_mut(9)
664            .unwrap()
665            .is_propagated = true;
666        assert!(progress_map.get_leader_propagation_slot_must_exist(9).0);
667        assert!(progress_map.get(&9).unwrap().propagated_stats.is_propagated);
668
669        // Because slot 9 is now confirmed, then slot 10 is also confirmed b/c 9
670        // is the last leader slot before 10
671        assert!(progress_map.get_leader_propagation_slot_must_exist(10).0);
672
673        // If we make slot 10 a leader slot though, even though its previous
674        // leader slot 9 has been confirmed, slot 10 itself is not confirmed
675        progress_map
676            .get_propagated_stats_mut(10)
677            .unwrap()
678            .is_leader_slot = true;
679        assert!(!progress_map.get_leader_propagation_slot_must_exist(10).0);
680    }
681}