use crate::{ cluster_info_vote_listener::SlotVoteTracker, consensus::StakeLockout, replay_stage::SUPERMINORITY_THRESHOLD, }; use solana_ledger::{ bank_forks::BankForks, blockstore_processor::{ConfirmationProgress, ConfirmationTiming}, }; use solana_runtime::bank::Bank; use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey}; use std::{ collections::{HashMap, HashSet}, rc::Rc, sync::{Arc, RwLock}, }; #[derive(Default)] pub(crate) struct ReplaySlotStats(ConfirmationTiming); impl std::ops::Deref for ReplaySlotStats { type Target = ConfirmationTiming; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for ReplaySlotStats { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl ReplaySlotStats { pub fn report_stats(&self, slot: Slot, num_entries: usize, num_shreds: u64) { datapoint_info!( "replay-slot-stats", ("slot", slot as i64, i64), ("fetch_entries_time", self.fetch_elapsed as i64, i64), ( "fetch_entries_fail_time", self.fetch_fail_elapsed as i64, i64 ), ("entry_verification_time", self.verify_elapsed as i64, i64), ("replay_time", self.replay_elapsed as i64, i64), ( "replay_total_elapsed", self.started.elapsed().as_micros() as i64, i64 ), ("total_entries", num_entries as i64, i64), ("total_shreds", num_shreds as i64, i64), ); } } #[derive(Debug)] pub(crate) struct ValidatorStakeInfo { pub validator_vote_pubkey: Pubkey, pub stake: u64, pub total_epoch_stake: u64, } impl Default for ValidatorStakeInfo { fn default() -> Self { Self { stake: 0, validator_vote_pubkey: Pubkey::default(), total_epoch_stake: 1, } } } impl ValidatorStakeInfo { pub fn new(validator_vote_pubkey: Pubkey, stake: u64, total_epoch_stake: u64) -> Self { Self { validator_vote_pubkey, stake, total_epoch_stake, } } } pub(crate) struct ForkProgress { pub(crate) is_dead: bool, pub(crate) fork_stats: ForkStats, pub(crate) propagated_stats: PropagatedStats, pub(crate) replay_stats: ReplaySlotStats, pub(crate) replay_progress: ConfirmationProgress, } impl ForkProgress { pub fn new( last_entry: Hash, prev_leader_slot: Option, validator_stake_info: Option, ) -> Self { let ( is_leader_slot, propagated_validators_stake, propagated_validators, is_propagated, total_epoch_stake, ) = validator_stake_info .map(|info| { ( true, info.stake, vec![Rc::new(info.validator_vote_pubkey)] .into_iter() .collect(), { if info.total_epoch_stake == 0 { true } else { info.stake as f64 / info.total_epoch_stake as f64 > SUPERMINORITY_THRESHOLD } }, info.total_epoch_stake, ) }) .unwrap_or((false, 0, HashSet::new(), false, 0)); Self { is_dead: false, fork_stats: ForkStats::default(), replay_stats: ReplaySlotStats::default(), replay_progress: ConfirmationProgress::new(last_entry), propagated_stats: PropagatedStats { prev_leader_slot, is_leader_slot, propagated_validators_stake, propagated_validators, is_propagated, total_epoch_stake, ..PropagatedStats::default() }, } } pub fn new_from_bank( bank: &Bank, my_pubkey: &Pubkey, voting_pubkey: &Pubkey, prev_leader_slot: Option, ) -> Self { let validator_fork_info = { if bank.collector_id() == my_pubkey { let stake = bank.epoch_vote_account_stake(voting_pubkey); Some(ValidatorStakeInfo::new( *voting_pubkey, stake, bank.total_epoch_stake(), )) } else { None } }; Self::new(bank.last_blockhash(), prev_leader_slot, validator_fork_info) } } #[derive(Debug, Clone, Default)] pub(crate) struct ForkStats { pub(crate) weight: u128, pub(crate) fork_weight: u128, pub(crate) total_staked: u64, pub(crate) slot: Slot, pub(crate) block_height: u64, pub(crate) has_voted: bool, pub(crate) is_recent: bool, pub(crate) is_empty: bool, pub(crate) vote_threshold: bool, pub(crate) is_locked_out: bool, pub(crate) stake_lockouts: HashMap, pub(crate) confirmation_reported: bool, pub(crate) computed: bool, } #[derive(Clone, Default)] pub(crate) struct PropagatedStats { pub(crate) propagated_validators: HashSet>, pub(crate) propagated_validators_stake: u64, pub(crate) is_propagated: bool, pub(crate) is_leader_slot: bool, pub(crate) prev_leader_slot: Option, pub(crate) slot_vote_tracker: Option>>, pub(crate) total_epoch_stake: u64, } #[derive(Default)] pub(crate) struct ProgressMap { progress_map: HashMap, } impl std::ops::Deref for ProgressMap { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.progress_map } } impl std::ops::DerefMut for ProgressMap { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.progress_map } } impl ProgressMap { pub fn insert(&mut self, slot: Slot, fork_progress: ForkProgress) { self.progress_map.insert(slot, fork_progress); } pub fn get_propagated_stats(&self, slot: Slot) -> Option<&PropagatedStats> { self.progress_map .get(&slot) .map(|fork_progress| &fork_progress.propagated_stats) } pub fn get_propagated_stats_mut(&mut self, slot: Slot) -> Option<&mut PropagatedStats> { self.progress_map .get_mut(&slot) .map(|fork_progress| &mut fork_progress.propagated_stats) } pub fn get_fork_stats(&self, slot: Slot) -> Option<&ForkStats> { self.progress_map .get(&slot) .map(|fork_progress| &fork_progress.fork_stats) } pub fn get_fork_stats_mut(&mut self, slot: Slot) -> Option<&mut ForkStats> { self.progress_map .get_mut(&slot) .map(|fork_progress| &mut fork_progress.fork_stats) } pub fn is_propagated(&self, slot: Slot) -> bool { let leader_slot_to_check = self.get_latest_leader_slot(slot); // prev_leader_slot doesn't exist because already rooted // or this validator hasn't been scheduled as a leader // yet. In both cases the latest leader is vacuously // confirmed leader_slot_to_check .map(|leader_slot_to_check| { // If the leader's stats are None (isn't in the // progress map), this means that prev_leader slot is // rooted, so return true self.get_propagated_stats(leader_slot_to_check) .map(|stats| stats.is_propagated) .unwrap_or(true) }) .unwrap_or(true) } pub fn get_latest_leader_slot(&self, slot: Slot) -> Option { let propagated_stats = self .get_propagated_stats(slot) .expect("All frozen banks must exist in the Progress map"); if propagated_stats.is_leader_slot { Some(slot) } else { propagated_stats.prev_leader_slot } } pub fn get_bank_prev_leader_slot(&self, bank: &Bank) -> Option { let parent_slot = bank.parent_slot(); self.get_propagated_stats(parent_slot) .map(|stats| { if stats.is_leader_slot { Some(parent_slot) } else { stats.prev_leader_slot } }) .unwrap_or(None) } pub fn handle_new_root(&mut self, bank_forks: &BankForks) { self.progress_map .retain(|k, _| bank_forks.get(*k).is_some()); } } #[cfg(test)] mod test { use super::*; #[test] fn test_is_propagated_status_on_construction() { // If the given ValidatorStakeInfo == None, then this is not // a leader slot and is_propagated == false let progress = ForkProgress::new(Hash::default(), Some(9), None); assert!(!progress.propagated_stats.is_propagated); // If the stake is zero, then threshold is always achieved let progress = ForkProgress::new( Hash::default(), Some(9), Some(ValidatorStakeInfo { total_epoch_stake: 0, ..ValidatorStakeInfo::default() }), ); assert!(progress.propagated_stats.is_propagated); // If the stake is non zero, then threshold is not achieved unless // validator has enough stake by itself to pass threshold let progress = ForkProgress::new( Hash::default(), Some(9), Some(ValidatorStakeInfo { total_epoch_stake: 2, ..ValidatorStakeInfo::default() }), ); assert!(!progress.propagated_stats.is_propagated); // Give the validator enough stake by itself to pass threshold let progress = ForkProgress::new( Hash::default(), Some(9), Some(ValidatorStakeInfo { stake: 1, total_epoch_stake: 2, ..ValidatorStakeInfo::default() }), ); assert!(progress.propagated_stats.is_propagated); // Check that the default ValidatorStakeInfo::default() constructs a ForkProgress // with is_propagated == false, otherwise propagation tests will fail to run // the proper checks (most will auto-pass without checking anything) let progress = ForkProgress::new( Hash::default(), Some(9), Some(ValidatorStakeInfo::default()), ); assert!(!progress.propagated_stats.is_propagated); } #[test] fn test_is_propagated() { let mut progress_map = ProgressMap::default(); // Insert new ForkProgress for slot 10 (not a leader slot) and its // previous leader slot 9 (leader slot) progress_map.insert(10, ForkProgress::new(Hash::default(), Some(9), None)); progress_map.insert( 9, ForkProgress::new(Hash::default(), None, Some(ValidatorStakeInfo::default())), ); // None of these slot have parents which are confirmed assert!(!progress_map.is_propagated(9)); assert!(!progress_map.is_propagated(10)); // Insert new ForkProgress for slot 8 with no previous leader. // The previous leader before 8, slot 7, does not exist in // progress map, so is_propagated(8) should return true as // this implies the parent is rooted progress_map.insert(8, ForkProgress::new(Hash::default(), Some(7), None)); assert!(progress_map.is_propagated(8)); // If we set the is_propagated = true, is_propagated should return true progress_map .get_propagated_stats_mut(9) .unwrap() .is_propagated = true; assert!(progress_map.is_propagated(9)); assert!(progress_map.get(&9).unwrap().propagated_stats.is_propagated); // Because slot 9 is now confirmed, then slot 10 is also confirmed b/c 9 // is the last leader slot before 10 assert!(progress_map.is_propagated(10)); // If we make slot 10 a leader slot though, even though its previous // leader slot 9 has been confirmed, slot 10 itself is not confirmed progress_map .get_propagated_stats_mut(10) .unwrap() .is_leader_slot = true; assert!(!progress_map.is_propagated(10)); } }