Introduce slot dumping to ReplayStage (#18160)
This commit is contained in:
parent
27cc7577a1
commit
4d3e301ee4
|
@ -3,16 +3,20 @@
|
|||
extern crate solana_core;
|
||||
extern crate test;
|
||||
|
||||
use solana_core::consensus::Tower;
|
||||
use solana_core::{consensus::Tower, vote_simulator::VoteSimulator};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_forks::BankForks;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use test::Bencher;
|
||||
use trees::tr;
|
||||
|
||||
#[bench]
|
||||
fn bench_save_tower(bench: &mut Bencher) {
|
||||
|
@ -34,3 +38,46 @@ fn bench_save_tower(bench: &mut Bencher) {
|
|||
tower.save(&node_keypair).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_generate_ancestors_descendants(bench: &mut Bencher) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let path = dir.path();
|
||||
|
||||
let vote_account_pubkey = &Pubkey::default();
|
||||
let node_keypair = Arc::new(Keypair::new());
|
||||
let heaviest_bank = BankForks::new(Bank::default()).working_bank();
|
||||
let mut tower = Tower::new(
|
||||
&node_keypair.pubkey(),
|
||||
vote_account_pubkey,
|
||||
0,
|
||||
&heaviest_bank,
|
||||
path,
|
||||
);
|
||||
|
||||
let num_banks = 500;
|
||||
let forks = tr(0);
|
||||
let mut vote_simulator = VoteSimulator::new(2);
|
||||
vote_simulator.fill_bank_forks(forks, &HashMap::new());
|
||||
vote_simulator.create_and_vote_new_branch(
|
||||
0,
|
||||
num_banks,
|
||||
&HashMap::new(),
|
||||
&HashSet::new(),
|
||||
&Pubkey::new_unique(),
|
||||
&mut tower,
|
||||
);
|
||||
|
||||
bench.iter(move || {
|
||||
for _ in 0..num_banks {
|
||||
let _ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
|
||||
let _descendants = vote_simulator
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.descendants()
|
||||
.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ use crate::{
|
|||
progress_map::ProgressMap,
|
||||
};
|
||||
use solana_sdk::{clock::Slot, hash::Hash};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
|
||||
pub(crate) type DuplicateSlotsTracker = BTreeSet<Slot>;
|
||||
pub(crate) type DuplicateSlotsToRepair = HashSet<(Slot, Hash)>;
|
||||
pub(crate) type GossipDuplicateConfirmedSlots = BTreeMap<Slot, Hash>;
|
||||
type SlotStateHandler = fn(Slot, &Hash, Option<&Hash>, bool, bool) -> Vec<ResultingStateChange>;
|
||||
|
||||
|
@ -39,8 +40,6 @@ impl SlotStateUpdate {
|
|||
}
|
||||
}
|
||||
|
||||
fn repair_correct_version(_slot: Slot, _hash: &Hash) {}
|
||||
|
||||
fn on_dead_slot(
|
||||
slot: Slot,
|
||||
bank_frozen_hash: &Hash,
|
||||
|
@ -202,6 +201,7 @@ fn get_cluster_duplicate_confirmed_hash<'a>(
|
|||
fn apply_state_changes(
|
||||
slot: Slot,
|
||||
fork_choice: &mut HeaviestSubtreeForkChoice,
|
||||
duplicate_slots_to_repair: &mut DuplicateSlotsToRepair,
|
||||
state_changes: Vec<ResultingStateChange>,
|
||||
) {
|
||||
for state_change in state_changes {
|
||||
|
@ -212,9 +212,7 @@ fn apply_state_changes(
|
|||
ResultingStateChange::RepairDuplicateConfirmedVersion(
|
||||
cluster_duplicate_confirmed_hash,
|
||||
) => {
|
||||
// TODO: Should consider moving the updating of the duplicate slots in the
|
||||
// progress map from ReplayStage::confirm_forks to here.
|
||||
repair_correct_version(slot, &cluster_duplicate_confirmed_hash);
|
||||
duplicate_slots_to_repair.insert((slot, cluster_duplicate_confirmed_hash));
|
||||
}
|
||||
ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(bank_frozen_hash) => {
|
||||
fork_choice.mark_fork_valid_candidate(&(slot, bank_frozen_hash));
|
||||
|
@ -232,6 +230,7 @@ pub(crate) fn check_slot_agrees_with_cluster(
|
|||
gossip_duplicate_confirmed_slots: &GossipDuplicateConfirmedSlots,
|
||||
progress: &ProgressMap,
|
||||
fork_choice: &mut HeaviestSubtreeForkChoice,
|
||||
duplicate_slots_to_repair: &mut HashSet<(Slot, Hash)>,
|
||||
slot_state_update: SlotStateUpdate,
|
||||
) {
|
||||
info!(
|
||||
|
@ -300,17 +299,17 @@ pub(crate) fn check_slot_agrees_with_cluster(
|
|||
is_slot_duplicate,
|
||||
is_dead,
|
||||
);
|
||||
apply_state_changes(slot, fork_choice, state_changes);
|
||||
apply_state_changes(slot, fork_choice, duplicate_slots_to_repair, state_changes);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::consensus::test::VoteSimulator;
|
||||
use crate::vote_simulator::VoteSimulator;
|
||||
use solana_runtime::bank_forks::BankForks;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::RwLock,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use trees::tr;
|
||||
|
||||
|
@ -318,7 +317,7 @@ mod test {
|
|||
heaviest_subtree_fork_choice: HeaviestSubtreeForkChoice,
|
||||
progress: ProgressMap,
|
||||
descendants: HashMap<Slot, HashSet<Slot>>,
|
||||
bank_forks: RwLock<BankForks>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
}
|
||||
|
||||
fn setup() -> InitialState {
|
||||
|
@ -613,6 +612,8 @@ mod test {
|
|||
..
|
||||
} = setup();
|
||||
|
||||
let mut duplicate_slots_to_repair = DuplicateSlotsToRepair::default();
|
||||
|
||||
// MarkSlotDuplicate should mark progress map and remove
|
||||
// the slot from fork choice
|
||||
let duplicate_slot = bank_forks.read().unwrap().root() + 1;
|
||||
|
@ -625,6 +626,7 @@ mod test {
|
|||
apply_state_changes(
|
||||
duplicate_slot,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
vec![ResultingStateChange::MarkSlotDuplicate(duplicate_slot_hash)],
|
||||
);
|
||||
assert!(!heaviest_subtree_fork_choice
|
||||
|
@ -646,11 +648,13 @@ mod test {
|
|||
duplicate_slot
|
||||
);
|
||||
}
|
||||
assert!(duplicate_slots_to_repair.is_empty());
|
||||
|
||||
// DuplicateConfirmedSlotMatchesCluster should re-enable fork choice
|
||||
apply_state_changes(
|
||||
duplicate_slot,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(
|
||||
duplicate_slot_hash,
|
||||
)],
|
||||
|
@ -671,6 +675,22 @@ mod test {
|
|||
assert!(heaviest_subtree_fork_choice
|
||||
.is_candidate(&(duplicate_slot, duplicate_slot_hash))
|
||||
.unwrap());
|
||||
assert!(duplicate_slots_to_repair.is_empty());
|
||||
|
||||
// Simulate detecting another hash that is the correct version,
|
||||
// RepairDuplicateConfirmedVersion should add the slot to repair
|
||||
// to `duplicate_slots_to_repair`
|
||||
let correct_hash = Hash::new_unique();
|
||||
apply_state_changes(
|
||||
duplicate_slot,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
vec![ResultingStateChange::RepairDuplicateConfirmedVersion(
|
||||
correct_hash,
|
||||
)],
|
||||
);
|
||||
assert_eq!(duplicate_slots_to_repair.len(), 1);
|
||||
assert!(duplicate_slots_to_repair.contains(&(duplicate_slot, correct_hash)));
|
||||
}
|
||||
|
||||
fn run_test_state_duplicate_then_bank_frozen(initial_bank_hash: Option<Hash>) {
|
||||
|
@ -689,6 +709,7 @@ mod test {
|
|||
let root = 0;
|
||||
let mut duplicate_slots_tracker = DuplicateSlotsTracker::default();
|
||||
let gossip_duplicate_confirmed_slots = GossipDuplicateConfirmedSlots::default();
|
||||
let mut duplicate_slots_to_repair = DuplicateSlotsToRepair::default();
|
||||
let duplicate_slot = 2;
|
||||
check_slot_agrees_with_cluster(
|
||||
duplicate_slot,
|
||||
|
@ -698,6 +719,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
SlotStateUpdate::Duplicate,
|
||||
);
|
||||
assert!(duplicate_slots_tracker.contains(&duplicate_slot));
|
||||
|
@ -724,6 +746,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
SlotStateUpdate::Frozen,
|
||||
);
|
||||
|
||||
|
@ -785,6 +808,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsToRepair::default(),
|
||||
SlotStateUpdate::DuplicateConfirmed,
|
||||
);
|
||||
assert!(heaviest_subtree_fork_choice
|
||||
|
@ -814,6 +838,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsToRepair::default(),
|
||||
SlotStateUpdate::Duplicate,
|
||||
);
|
||||
assert!(duplicate_slots_tracker.contains(&3));
|
||||
|
@ -872,6 +897,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsToRepair::default(),
|
||||
SlotStateUpdate::Duplicate,
|
||||
);
|
||||
assert!(duplicate_slots_tracker.contains(&2));
|
||||
|
@ -901,6 +927,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsToRepair::default(),
|
||||
SlotStateUpdate::DuplicateConfirmed,
|
||||
);
|
||||
for slot in 0..=3 {
|
||||
|
@ -936,6 +963,7 @@ mod test {
|
|||
let root = 0;
|
||||
let mut duplicate_slots_tracker = DuplicateSlotsTracker::default();
|
||||
let mut gossip_duplicate_confirmed_slots = GossipDuplicateConfirmedSlots::default();
|
||||
let mut duplicate_slots_to_repair = DuplicateSlotsToRepair::default();
|
||||
|
||||
// Mark 3 as duplicate confirmed
|
||||
gossip_duplicate_confirmed_slots.insert(3, slot3_hash);
|
||||
|
@ -947,6 +975,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
SlotStateUpdate::DuplicateConfirmed,
|
||||
);
|
||||
let verify_all_slots_duplicate_confirmed =
|
||||
|
@ -980,6 +1009,7 @@ mod test {
|
|||
&gossip_duplicate_confirmed_slots,
|
||||
&progress,
|
||||
&mut heaviest_subtree_fork_choice,
|
||||
&mut duplicate_slots_to_repair,
|
||||
SlotStateUpdate::Duplicate,
|
||||
);
|
||||
assert!(duplicate_slots_tracker.contains(&1));
|
||||
|
|
|
@ -570,7 +570,7 @@ impl Tower {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn make_check_switch_threshold_decision(
|
||||
&self,
|
||||
switch_slot: u64,
|
||||
switch_slot: Slot,
|
||||
ancestors: &HashMap<Slot, HashSet<u64>>,
|
||||
descendants: &HashMap<Slot, HashSet<u64>>,
|
||||
progress: &ProgressMap,
|
||||
|
@ -641,6 +641,34 @@ impl Tower {
|
|||
SwitchForkDecision::FailedSwitchDuplicateRollback(latest_duplicate_ancestor)
|
||||
};
|
||||
|
||||
// `heaviest_subtree_fork_choice` entries are not cleaned by duplicate block purging/rollback logic,
|
||||
// so this is safe to check here. We return here if the last voted slot was rolled back/purged due to
|
||||
// being a duplicate because `ancestors`/`descendants`/`progress` structurs may be missing this slot due
|
||||
// to duplicate purging. This would cause many of the `unwrap()` checks below to fail.
|
||||
//
|
||||
// TODO: Handle if the last vote is on a dupe, and then we restart. The dupe won't be in
|
||||
// heaviest_subtree_fork_choice, so `heaviest_subtree_fork_choice.latest_invalid_ancestor()` will return
|
||||
// None, but the last vote will be persisted in tower.
|
||||
let switch_hash = progress.get_hash(switch_slot).expect("Slot we're trying to switch to must exist AND be frozen in progress map");
|
||||
if let Some(latest_duplicate_ancestor) = heaviest_subtree_fork_choice.latest_invalid_ancestor(&(last_voted_slot, last_voted_hash)) {
|
||||
// We're rolling back because one of the ancestors of the last vote was a duplicate. In this
|
||||
// case, it's acceptable if the switch candidate is one of ancestors of the previous vote,
|
||||
// just fail the switch check because there's no point in voting on an ancestor. ReplayStage
|
||||
// should then have a special case continue building an alternate fork from this ancestor, NOT
|
||||
// the `last_voted_slot`. This is in contrast to usual SwitchFailure where ReplayStage continues to build blocks
|
||||
// on latest vote. See `ReplayStage::select_vote_and_reset_forks()` for more details.
|
||||
if heaviest_subtree_fork_choice.is_strict_ancestor(&(switch_slot, switch_hash), &(last_voted_slot, last_voted_hash)) {
|
||||
return rollback_due_to_to_to_duplicate_ancestor(latest_duplicate_ancestor);
|
||||
} else if progress.get_hash(last_voted_slot).map(|current_slot_hash| current_slot_hash != last_voted_hash).unwrap_or(true) {
|
||||
// Our last vote slot was purged because it was on a duplicate fork, don't continue below
|
||||
// where checks may panic. We allow a freebie vote here that may violate switching
|
||||
// thresholds
|
||||
// TODO: Properly handle this case
|
||||
info!("Allowing switch vote on {:?} because last vote {:?} was rolled back", (switch_slot, switch_hash), (last_voted_slot, last_voted_hash));
|
||||
return SwitchForkDecision::SwitchProof(Hash::default());
|
||||
}
|
||||
}
|
||||
|
||||
let last_vote_ancestors =
|
||||
ancestors.get(&last_voted_slot).unwrap_or_else(|| {
|
||||
if self.is_stray_last_vote() {
|
||||
|
@ -669,14 +697,6 @@ impl Tower {
|
|||
if last_vote_ancestors.contains(&switch_slot) {
|
||||
if self.is_stray_last_vote() {
|
||||
return suspended_decision_due_to_major_unsynced_ledger();
|
||||
} else if let Some(latest_duplicate_ancestor) = heaviest_subtree_fork_choice.latest_invalid_ancestor(&(last_voted_slot, last_voted_hash)) {
|
||||
// We're rolling back because one of the ancestors of the last vote was a duplicate. In this
|
||||
// case, it's acceptable if the switch candidate is one of ancestors of the previous vote,
|
||||
// just fail the switch check because there's no point in voting on an ancestor. ReplayStage
|
||||
// should then have a special case continue building an alternate fork from this ancestor, NOT
|
||||
// the `last_voted_slot`. This is in contrast to usual SwitchFailure where ReplayStage continues to build blocks
|
||||
// on latest vote. See `select_vote_and_reset_forks()` for more details.
|
||||
return rollback_due_to_to_to_duplicate_ancestor(latest_duplicate_ancestor);
|
||||
} else {
|
||||
panic!(
|
||||
"Should never consider switching to ancestor ({}) of last vote: {}, ancestors({:?})",
|
||||
|
@ -820,7 +840,7 @@ impl Tower {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_switch_threshold(
|
||||
&mut self,
|
||||
switch_slot: u64,
|
||||
switch_slot: Slot,
|
||||
ancestors: &HashMap<Slot, HashSet<u64>>,
|
||||
descendants: &HashMap<Slot, HashSet<u64>>,
|
||||
progress: &ProgressMap,
|
||||
|
@ -1356,24 +1376,11 @@ pub fn reconcile_blockstore_roots_with_tower(
|
|||
pub mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cluster_info_vote_listener::VoteTracker,
|
||||
cluster_slot_state_verifier::{DuplicateSlotsTracker, GossipDuplicateConfirmedSlots},
|
||||
cluster_slots::ClusterSlots,
|
||||
fork_choice::{ForkChoice, SelectVoteAndResetForkResult},
|
||||
heaviest_subtree_fork_choice::SlotHashKey,
|
||||
progress_map::ForkProgress,
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
unfrozen_gossip_verified_vote_hashes::UnfrozenGossipVerifiedVoteHashes,
|
||||
fork_choice::ForkChoice, heaviest_subtree_fork_choice::SlotHashKey,
|
||||
replay_stage::HeaviestForkFailures, vote_simulator::VoteSimulator,
|
||||
};
|
||||
use solana_ledger::{blockstore::make_slot_entries, get_tmp_ledger_path};
|
||||
use solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender,
|
||||
bank::Bank,
|
||||
bank_forks::BankForks,
|
||||
genesis_utils::{
|
||||
create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
|
||||
},
|
||||
};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
|
||||
clock::Slot,
|
||||
|
@ -1382,338 +1389,15 @@ pub mod test {
|
|||
signature::Signer,
|
||||
slot_history::SlotHistory,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY},
|
||||
vote_transaction,
|
||||
};
|
||||
use solana_vote_program::vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{remove_file, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use trees::{tr, Tree, TreeWalk};
|
||||
|
||||
pub(crate) struct VoteSimulator {
|
||||
pub validator_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
pub node_pubkeys: Vec<Pubkey>,
|
||||
pub vote_pubkeys: Vec<Pubkey>,
|
||||
pub bank_forks: RwLock<BankForks>,
|
||||
pub progress: ProgressMap,
|
||||
pub heaviest_subtree_fork_choice: HeaviestSubtreeForkChoice,
|
||||
pub latest_validator_votes_for_frozen_banks: LatestValidatorVotesForFrozenBanks,
|
||||
}
|
||||
|
||||
impl VoteSimulator {
|
||||
pub(crate) fn new(num_keypairs: usize) -> Self {
|
||||
let (
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks,
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
) = Self::init_state(num_keypairs);
|
||||
Self {
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks: RwLock::new(bank_forks),
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
latest_validator_votes_for_frozen_banks:
|
||||
LatestValidatorVotesForFrozenBanks::default(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn fill_bank_forks(
|
||||
&mut self,
|
||||
forks: Tree<u64>,
|
||||
cluster_votes: &HashMap<Pubkey, Vec<u64>>,
|
||||
) {
|
||||
let root = *forks.root().data();
|
||||
assert!(self.bank_forks.read().unwrap().get(root).is_some());
|
||||
|
||||
let mut walk = TreeWalk::from(forks);
|
||||
|
||||
while let Some(visit) = walk.get() {
|
||||
let slot = *visit.node().data();
|
||||
if self.bank_forks.read().unwrap().get(slot).is_some() {
|
||||
walk.forward();
|
||||
continue;
|
||||
}
|
||||
let parent = *walk.get_parent().unwrap().data();
|
||||
let parent_bank = self.bank_forks.read().unwrap().get(parent).unwrap().clone();
|
||||
let new_bank = Bank::new_from_parent(&parent_bank, &Pubkey::default(), slot);
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0));
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent) {
|
||||
let keypairs = self.validator_keypairs.get(pubkey).unwrap();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let vote_tx = vote_transaction::new_vote_transaction(
|
||||
// Must vote > root to be processed
|
||||
vec![parent],
|
||||
parent_bank.hash(),
|
||||
last_blockhash,
|
||||
&keypairs.node_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
None,
|
||||
);
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
self.heaviest_subtree_fork_choice.add_new_leaf_slot(
|
||||
(new_bank.slot(), new_bank.hash()),
|
||||
Some((new_bank.parent_slot(), new_bank.parent_hash())),
|
||||
);
|
||||
self.bank_forks.write().unwrap().insert(new_bank);
|
||||
walk.forward();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn simulate_vote(
|
||||
&mut self,
|
||||
vote_slot: Slot,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
) -> Vec<HeaviestForkFailures> {
|
||||
// Try to simulate the vote
|
||||
let my_keypairs = self.validator_keypairs.get(my_pubkey).unwrap();
|
||||
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
|
||||
let ancestors = self.bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.frozen_banks()
|
||||
.values()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let _ = ReplayStage::compute_bank_stats(
|
||||
my_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
tower,
|
||||
&mut self.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
&self.bank_forks,
|
||||
&mut self.heaviest_subtree_fork_choice,
|
||||
&mut self.latest_validator_votes_for_frozen_banks,
|
||||
);
|
||||
|
||||
let vote_bank = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(vote_slot)
|
||||
.expect("Bank must have been created before vote simulation")
|
||||
.clone();
|
||||
|
||||
// Try to vote on the given slot
|
||||
let descendants = self.bank_forks.read().unwrap().descendants().clone();
|
||||
let SelectVoteAndResetForkResult {
|
||||
heaviest_fork_failures,
|
||||
..
|
||||
} = ReplayStage::select_vote_and_reset_forks(
|
||||
&vote_bank,
|
||||
None,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&self.progress,
|
||||
tower,
|
||||
&self.latest_validator_votes_for_frozen_banks,
|
||||
&self.heaviest_subtree_fork_choice,
|
||||
);
|
||||
|
||||
// Make sure this slot isn't locked out or failing threshold
|
||||
info!("Checking vote: {}", vote_bank.slot());
|
||||
if !heaviest_fork_failures.is_empty() {
|
||||
return heaviest_fork_failures;
|
||||
}
|
||||
|
||||
let new_root = tower.record_bank_vote(&vote_bank, &my_vote_pubkey);
|
||||
if let Some(new_root) = new_root {
|
||||
self.set_root(new_root);
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub fn set_root(&mut self, new_root: Slot) {
|
||||
ReplayStage::handle_new_root(
|
||||
new_root,
|
||||
&self.bank_forks,
|
||||
&mut self.progress,
|
||||
&AbsRequestSender::default(),
|
||||
None,
|
||||
&mut self.heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsTracker::default(),
|
||||
&mut GossipDuplicateConfirmedSlots::default(),
|
||||
&mut UnfrozenGossipVerifiedVoteHashes::default(),
|
||||
&mut true,
|
||||
&mut Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn create_and_vote_new_branch(
|
||||
&mut self,
|
||||
start_slot: Slot,
|
||||
end_slot: Slot,
|
||||
cluster_votes: &HashMap<Pubkey, Vec<u64>>,
|
||||
votes_to_simulate: &HashSet<Slot>,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
) -> HashMap<Slot, Vec<HeaviestForkFailures>> {
|
||||
(start_slot + 1..=end_slot)
|
||||
.filter_map(|slot| {
|
||||
let mut fork_tip_parent = tr(slot - 1);
|
||||
fork_tip_parent.push_front(tr(slot));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if votes_to_simulate.contains(&slot) {
|
||||
Some((slot, self.simulate_vote(slot, my_pubkey, tower)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn simulate_lockout_interval(
|
||||
&mut self,
|
||||
slot: Slot,
|
||||
lockout_interval: (u64, u64),
|
||||
vote_account_pubkey: &Pubkey,
|
||||
) {
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0))
|
||||
.fork_stats
|
||||
.lockout_intervals
|
||||
.entry(lockout_interval.1)
|
||||
.or_default()
|
||||
.push((lockout_interval.0, *vote_account_pubkey));
|
||||
}
|
||||
|
||||
fn can_progress_on_fork(
|
||||
&mut self,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
|
||||
for i in 1..num_slots {
|
||||
// The parent of the tip of the fork
|
||||
let mut fork_tip_parent = tr(start_slot + i - 1);
|
||||
// The tip of the fork
|
||||
fork_tip_parent.push_front(tr(start_slot + i));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if self
|
||||
.simulate_vote(i + start_slot, my_pubkey, tower)
|
||||
.is_empty()
|
||||
{
|
||||
cluster_votes
|
||||
.entry(*my_pubkey)
|
||||
.or_default()
|
||||
.push(start_slot + i);
|
||||
}
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn init_state(
|
||||
num_keypairs: usize,
|
||||
) -> (
|
||||
HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
Vec<Pubkey>,
|
||||
Vec<Pubkey>,
|
||||
BankForks,
|
||||
ProgressMap,
|
||||
HeaviestSubtreeForkChoice,
|
||||
) {
|
||||
let keypairs: HashMap<_, _> = std::iter::repeat_with(|| {
|
||||
let vote_keypairs = ValidatorVoteKeypairs::new_rand();
|
||||
(vote_keypairs.node_keypair.pubkey(), vote_keypairs)
|
||||
})
|
||||
.take(num_keypairs)
|
||||
.collect();
|
||||
let node_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.node_keypair.pubkey())
|
||||
.collect();
|
||||
let vote_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.vote_keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
let (bank_forks, progress, heaviest_subtree_fork_choice) =
|
||||
initialize_state(&keypairs, 10_000);
|
||||
(
|
||||
keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks,
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup BankForks with bank 0 and all the validator accounts
|
||||
pub(crate) fn initialize_state(
|
||||
validator_keypairs_map: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
stake: u64,
|
||||
) -> (BankForks, ProgressMap, HeaviestSubtreeForkChoice) {
|
||||
let validator_keypairs: Vec<_> = validator_keypairs_map.values().collect();
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
voting_keypair: _,
|
||||
} = create_genesis_config_with_vote_accounts(
|
||||
1_000_000_000,
|
||||
&validator_keypairs,
|
||||
vec![stake; validator_keypairs.len()],
|
||||
);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
for pubkey in validator_keypairs_map.keys() {
|
||||
bank0.transfer(10_000, &mint_keypair, pubkey).unwrap();
|
||||
}
|
||||
|
||||
bank0.freeze();
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new_from_bank(
|
||||
&bank0,
|
||||
bank0.collector_id(),
|
||||
&Pubkey::default(),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
let bank_forks = BankForks::new(bank0);
|
||||
let heaviest_subtree_fork_choice =
|
||||
HeaviestSubtreeForkChoice::new_from_bank_forks(&bank_forks);
|
||||
(bank_forks, progress, heaviest_subtree_fork_choice)
|
||||
}
|
||||
use trees::tr;
|
||||
|
||||
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, ArcVoteAccount))> {
|
||||
let mut stakes = vec![];
|
||||
|
|
|
@ -10,13 +10,13 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub(crate) struct SelectVoteAndResetForkResult {
|
||||
pub struct SelectVoteAndResetForkResult {
|
||||
pub vote_bank: Option<(Arc<Bank>, SwitchForkDecision)>,
|
||||
pub reset_bank: Option<Arc<Bank>>,
|
||||
pub heaviest_fork_failures: Vec<HeaviestForkFailures>,
|
||||
}
|
||||
|
||||
pub(crate) trait ForkChoice {
|
||||
pub trait ForkChoice {
|
||||
type ForkChoiceKey;
|
||||
fn compute_bank_stats(
|
||||
&mut self,
|
||||
|
|
|
@ -166,7 +166,7 @@ pub struct HeaviestSubtreeForkChoice {
|
|||
}
|
||||
|
||||
impl HeaviestSubtreeForkChoice {
|
||||
pub(crate) fn new(root: SlotHashKey) -> Self {
|
||||
pub fn new(root: SlotHashKey) -> Self {
|
||||
let mut heaviest_subtree_fork_choice = Self {
|
||||
root,
|
||||
// Doesn't implement default because `root` must
|
||||
|
@ -181,7 +181,7 @@ impl HeaviestSubtreeForkChoice {
|
|||
|
||||
// Given a root and a list of `frozen_banks` sorted smallest to greatest by slot,
|
||||
// return a new HeaviestSubtreeForkChoice
|
||||
pub(crate) fn new_from_frozen_banks(root: SlotHashKey, frozen_banks: &[Arc<Bank>]) -> Self {
|
||||
pub fn new_from_frozen_banks(root: SlotHashKey, frozen_banks: &[Arc<Bank>]) -> Self {
|
||||
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new(root);
|
||||
let mut prev_slot = root.0;
|
||||
for bank in frozen_banks.iter() {
|
||||
|
@ -204,8 +204,7 @@ impl HeaviestSubtreeForkChoice {
|
|||
heaviest_subtree_fork_choice
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_from_bank_forks(bank_forks: &BankForks) -> Self {
|
||||
pub fn new_from_bank_forks(bank_forks: &BankForks) -> Self {
|
||||
let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect();
|
||||
|
||||
frozen_banks.sort_by_key(|bank| bank.slot());
|
||||
|
@ -214,7 +213,7 @@ impl HeaviestSubtreeForkChoice {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_from_tree<T: GetSlotHash>(forks: Tree<T>) -> Self {
|
||||
pub fn new_from_tree<T: GetSlotHash>(forks: Tree<T>) -> Self {
|
||||
let root = forks.root().data().slot_hash();
|
||||
let mut walk = TreeWalk::from(forks);
|
||||
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new(root);
|
||||
|
@ -476,6 +475,27 @@ impl HeaviestSubtreeForkChoice {
|
|||
.map(|fork_info| fork_info.is_candidate())
|
||||
}
|
||||
|
||||
/// Returns if a node with slot `maybe_ancestor_slot` is an ancestor of the node with
|
||||
/// key `node_key`
|
||||
pub fn is_strict_ancestor(
|
||||
&self,
|
||||
maybe_ancestor_key: &SlotHashKey,
|
||||
node_key: &SlotHashKey,
|
||||
) -> bool {
|
||||
if maybe_ancestor_key == node_key {
|
||||
return false;
|
||||
}
|
||||
|
||||
if maybe_ancestor_key.0 > node_key.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut ancestor_iterator = self.ancestor_iterator(*node_key);
|
||||
ancestor_iterator.any(|(ancestor_slot, ancestor_hash)| {
|
||||
ancestor_slot == maybe_ancestor_key.0 && ancestor_hash == maybe_ancestor_key.1
|
||||
})
|
||||
}
|
||||
|
||||
fn propagate_new_leaf(
|
||||
&mut self,
|
||||
slot_hash_key: &SlotHashKey,
|
||||
|
@ -942,8 +962,9 @@ impl ForkChoice for HeaviestSubtreeForkChoice {
|
|||
bank_forks: &RwLock<BankForks>,
|
||||
) -> (Arc<Bank>, Option<Arc<Bank>>) {
|
||||
let r_bank_forks = bank_forks.read().unwrap();
|
||||
|
||||
// BankForks should only contain one valid version of this slot
|
||||
(
|
||||
// BankForks should only contain one valid version of this slot
|
||||
r_bank_forks
|
||||
.get_with_checked_hash(self.best_overall_slot())
|
||||
.unwrap()
|
||||
|
@ -1045,7 +1066,7 @@ impl<'a> Iterator for AncestorIterator<'a> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::consensus::test::VoteSimulator;
|
||||
use crate::vote_simulator::VoteSimulator;
|
||||
use solana_runtime::{bank::Bank, bank_utils};
|
||||
use solana_sdk::{hash::Hash, slot_history::SlotHistory};
|
||||
use std::{collections::HashSet, ops::Range};
|
||||
|
|
|
@ -3,7 +3,7 @@ use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
|||
use std::collections::{hash_map::Entry, HashMap};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct LatestValidatorVotesForFrozenBanks {
|
||||
pub struct LatestValidatorVotesForFrozenBanks {
|
||||
// TODO: Clean outdated/unstaked pubkeys from this list.
|
||||
max_gossip_frozen_votes: HashMap<Pubkey, (Slot, Vec<Hash>)>,
|
||||
max_replay_frozen_votes: HashMap<Pubkey, (Slot, Vec<Hash>)>,
|
||||
|
@ -15,7 +15,7 @@ pub(crate) struct LatestValidatorVotesForFrozenBanks {
|
|||
impl LatestValidatorVotesForFrozenBanks {
|
||||
// `frozen_hash.is_some()` if the bank with slot == `vote_slot` is frozen
|
||||
// Returns whether the vote was actually added, and the latest voted frozen slot
|
||||
pub(crate) fn check_add_vote(
|
||||
pub fn check_add_vote(
|
||||
&mut self,
|
||||
vote_pubkey: Pubkey,
|
||||
vote_slot: Slot,
|
||||
|
@ -86,7 +86,7 @@ impl LatestValidatorVotesForFrozenBanks {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn take_votes_dirty_set(&mut self, root: Slot) -> Vec<(Pubkey, SlotHashKey)> {
|
||||
pub fn take_votes_dirty_set(&mut self, root: Slot) -> Vec<(Pubkey, SlotHashKey)> {
|
||||
let new_votes = std::mem::take(&mut self.fork_choice_dirty_set);
|
||||
new_votes
|
||||
.into_iter()
|
||||
|
@ -100,7 +100,7 @@ impl LatestValidatorVotesForFrozenBanks {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn max_gossip_frozen_votes(&self) -> &HashMap<Pubkey, (Slot, Vec<Hash>)> {
|
||||
pub fn max_gossip_frozen_votes(&self) -> &HashMap<Pubkey, (Slot, Vec<Hash>)> {
|
||||
&self.max_gossip_frozen_votes
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ pub mod tvu;
|
|||
pub mod unfrozen_gossip_verified_vote_hashes;
|
||||
pub mod validator;
|
||||
pub mod verified_vote_packets;
|
||||
pub mod vote_simulator;
|
||||
pub mod vote_stake_tracker;
|
||||
pub mod window_service;
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ impl OptimisticConfirmationVerifier {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::consensus::test::VoteSimulator;
|
||||
use crate::vote_simulator::VoteSimulator;
|
||||
use solana_ledger::get_tmp_ledger_path;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
|
|
@ -14,10 +14,10 @@ use std::{
|
|||
|
||||
type VotedSlot = Slot;
|
||||
type ExpirationSlot = Slot;
|
||||
pub(crate) type LockoutIntervals = BTreeMap<ExpirationSlot, Vec<(VotedSlot, Pubkey)>>;
|
||||
pub type LockoutIntervals = BTreeMap<ExpirationSlot, Vec<(VotedSlot, Pubkey)>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ReplaySlotStats(ConfirmationTiming);
|
||||
pub struct ReplaySlotStats(ConfirmationTiming);
|
||||
impl std::ops::Deref for ReplaySlotStats {
|
||||
type Target = ConfirmationTiming;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -139,7 +139,7 @@ impl ReplaySlotStats {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ValidatorStakeInfo {
|
||||
pub struct ValidatorStakeInfo {
|
||||
pub validator_vote_pubkey: Pubkey,
|
||||
pub stake: u64,
|
||||
pub total_epoch_stake: u64,
|
||||
|
@ -165,18 +165,18 @@ impl ValidatorStakeInfo {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
pub struct ForkProgress {
|
||||
pub is_dead: bool,
|
||||
pub fork_stats: ForkStats,
|
||||
pub propagated_stats: PropagatedStats,
|
||||
pub replay_stats: ReplaySlotStats,
|
||||
pub replay_progress: ConfirmationProgress,
|
||||
// Note `num_blocks_on_fork` and `num_dropped_blocks_on_fork` only
|
||||
// count new blocks replayed since last restart, which won't include
|
||||
// blocks already existing in the ledger/before snapshot at start,
|
||||
// so these stats do not span all of time
|
||||
pub(crate) num_blocks_on_fork: u64,
|
||||
pub(crate) num_dropped_blocks_on_fork: u64,
|
||||
pub num_blocks_on_fork: u64,
|
||||
pub num_dropped_blocks_on_fork: u64,
|
||||
}
|
||||
|
||||
impl ForkProgress {
|
||||
|
@ -211,6 +211,7 @@ impl ForkProgress {
|
|||
)
|
||||
})
|
||||
.unwrap_or((false, 0, HashSet::new(), false, 0));
|
||||
|
||||
Self {
|
||||
is_dead: false,
|
||||
fork_stats: ForkStats::default(),
|
||||
|
@ -250,46 +251,51 @@ impl ForkProgress {
|
|||
}
|
||||
};
|
||||
|
||||
Self::new(
|
||||
let mut new_progress = Self::new(
|
||||
bank.last_blockhash(),
|
||||
prev_leader_slot,
|
||||
validator_stake_info,
|
||||
num_blocks_on_fork,
|
||||
num_dropped_blocks_on_fork,
|
||||
)
|
||||
);
|
||||
|
||||
if bank.is_frozen() {
|
||||
new_progress.fork_stats.bank_hash = Some(bank.hash());
|
||||
}
|
||||
new_progress
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ForkStats {
|
||||
pub(crate) weight: u128,
|
||||
pub(crate) fork_weight: u128,
|
||||
pub(crate) total_stake: Stake,
|
||||
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) voted_stakes: VotedStakes,
|
||||
pub(crate) is_supermajority_confirmed: bool,
|
||||
pub(crate) computed: bool,
|
||||
pub(crate) lockout_intervals: LockoutIntervals,
|
||||
pub(crate) bank_hash: Option<Hash>,
|
||||
pub(crate) my_latest_landed_vote: Option<Slot>,
|
||||
pub struct ForkStats {
|
||||
pub weight: u128,
|
||||
pub fork_weight: u128,
|
||||
pub total_stake: Stake,
|
||||
pub block_height: u64,
|
||||
pub has_voted: bool,
|
||||
pub is_recent: bool,
|
||||
pub is_empty: bool,
|
||||
pub vote_threshold: bool,
|
||||
pub is_locked_out: bool,
|
||||
pub voted_stakes: VotedStakes,
|
||||
pub is_supermajority_confirmed: bool,
|
||||
pub computed: bool,
|
||||
pub lockout_intervals: LockoutIntervals,
|
||||
pub bank_hash: Option<Hash>,
|
||||
pub my_latest_landed_vote: Option<Slot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct PropagatedStats {
|
||||
pub(crate) propagated_validators: HashSet<Pubkey>,
|
||||
pub(crate) propagated_node_ids: HashSet<Pubkey>,
|
||||
pub(crate) propagated_validators_stake: u64,
|
||||
pub(crate) is_propagated: bool,
|
||||
pub(crate) is_leader_slot: bool,
|
||||
pub(crate) prev_leader_slot: Option<Slot>,
|
||||
pub(crate) slot_vote_tracker: Option<Arc<RwLock<SlotVoteTracker>>>,
|
||||
pub(crate) cluster_slot_pubkeys: Option<Arc<RwLock<SlotPubkeys>>>,
|
||||
pub(crate) total_epoch_stake: u64,
|
||||
pub struct PropagatedStats {
|
||||
pub propagated_validators: HashSet<Pubkey>,
|
||||
pub propagated_node_ids: HashSet<Pubkey>,
|
||||
pub propagated_validators_stake: u64,
|
||||
pub is_propagated: bool,
|
||||
pub is_leader_slot: bool,
|
||||
pub prev_leader_slot: Option<Slot>,
|
||||
pub slot_vote_tracker: Option<Arc<RwLock<SlotVoteTracker>>>,
|
||||
pub cluster_slot_pubkeys: Option<Arc<RwLock<SlotPubkeys>>>,
|
||||
pub total_epoch_stake: u64,
|
||||
}
|
||||
|
||||
impl PropagatedStats {
|
||||
|
@ -334,7 +340,7 @@ impl PropagatedStats {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ProgressMap {
|
||||
pub struct ProgressMap {
|
||||
progress_map: HashMap<Slot, ForkProgress>,
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@ use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct UnfrozenGossipVerifiedVoteHashes {
|
||||
pub struct UnfrozenGossipVerifiedVoteHashes {
|
||||
pub votes_per_slot: BTreeMap<Slot, HashMap<Hash, Vec<Pubkey>>>,
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ impl UnfrozenGossipVerifiedVoteHashes {
|
|||
// Update `latest_validator_votes_for_frozen_banks` if gossip has seen a newer vote
|
||||
// for a frozen bank.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn add_vote(
|
||||
pub fn add_vote(
|
||||
&mut self,
|
||||
pubkey: Pubkey,
|
||||
vote_slot: Slot,
|
||||
|
@ -46,13 +46,13 @@ impl UnfrozenGossipVerifiedVoteHashes {
|
|||
}
|
||||
|
||||
// Cleanup `votes_per_slot` based on new roots
|
||||
pub(crate) fn set_root(&mut self, new_root: Slot) {
|
||||
pub fn set_root(&mut self, new_root: Slot) {
|
||||
let mut slots_ge_root = self.votes_per_slot.split_off(&new_root);
|
||||
// `self.votes_per_slot` now only contains entries >= `new_root`
|
||||
std::mem::swap(&mut self.votes_per_slot, &mut slots_ge_root);
|
||||
}
|
||||
|
||||
pub(crate) fn remove_slot_hash(&mut self, slot: Slot, hash: &Hash) -> Option<Vec<Pubkey>> {
|
||||
pub fn remove_slot_hash(&mut self, slot: Slot, hash: &Hash) -> Option<Vec<Pubkey>> {
|
||||
self.votes_per_slot.get_mut(&slot).and_then(|slot_hashes| {
|
||||
slot_hashes.remove(hash)
|
||||
// If `slot_hashes` becomes empty, it'll be removed by `set_root()` later
|
||||
|
|
|
@ -0,0 +1,351 @@
|
|||
use crate::{
|
||||
cluster_info_vote_listener::VoteTracker,
|
||||
cluster_slot_state_verifier::{DuplicateSlotsTracker, GossipDuplicateConfirmedSlots},
|
||||
cluster_slots::ClusterSlots,
|
||||
consensus::Tower,
|
||||
fork_choice::SelectVoteAndResetForkResult,
|
||||
heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice,
|
||||
latest_validator_votes_for_frozen_banks::LatestValidatorVotesForFrozenBanks,
|
||||
progress_map::{ForkProgress, ProgressMap},
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
unfrozen_gossip_verified_vote_hashes::UnfrozenGossipVerifiedVoteHashes,
|
||||
};
|
||||
use solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender,
|
||||
bank::Bank,
|
||||
bank_forks::BankForks,
|
||||
genesis_utils::{
|
||||
create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
|
||||
},
|
||||
};
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signer};
|
||||
use solana_vote_program::vote_transaction;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use trees::{tr, Tree, TreeWalk};
|
||||
|
||||
pub struct VoteSimulator {
|
||||
pub validator_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
pub node_pubkeys: Vec<Pubkey>,
|
||||
pub vote_pubkeys: Vec<Pubkey>,
|
||||
pub bank_forks: Arc<RwLock<BankForks>>,
|
||||
pub progress: ProgressMap,
|
||||
pub heaviest_subtree_fork_choice: HeaviestSubtreeForkChoice,
|
||||
pub latest_validator_votes_for_frozen_banks: LatestValidatorVotesForFrozenBanks,
|
||||
}
|
||||
|
||||
impl VoteSimulator {
|
||||
pub fn new(num_keypairs: usize) -> Self {
|
||||
let (
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks,
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
) = Self::init_state(num_keypairs);
|
||||
Self {
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks: Arc::new(RwLock::new(bank_forks)),
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
latest_validator_votes_for_frozen_banks: LatestValidatorVotesForFrozenBanks::default(),
|
||||
}
|
||||
}
|
||||
pub fn fill_bank_forks(&mut self, forks: Tree<u64>, cluster_votes: &HashMap<Pubkey, Vec<u64>>) {
|
||||
let root = *forks.root().data();
|
||||
assert!(self.bank_forks.read().unwrap().get(root).is_some());
|
||||
|
||||
let mut walk = TreeWalk::from(forks);
|
||||
|
||||
while let Some(visit) = walk.get() {
|
||||
let slot = *visit.node().data();
|
||||
if self.bank_forks.read().unwrap().get(slot).is_some() {
|
||||
walk.forward();
|
||||
continue;
|
||||
}
|
||||
let parent = *walk.get_parent().unwrap().data();
|
||||
let parent_bank = self.bank_forks.read().unwrap().get(parent).unwrap().clone();
|
||||
let new_bank = Bank::new_from_parent(&parent_bank, &Pubkey::default(), slot);
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0));
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent) {
|
||||
let keypairs = self.validator_keypairs.get(pubkey).unwrap();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let vote_tx = vote_transaction::new_vote_transaction(
|
||||
// Must vote > root to be processed
|
||||
vec![parent],
|
||||
parent_bank.hash(),
|
||||
last_blockhash,
|
||||
&keypairs.node_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
None,
|
||||
);
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
|
||||
// Check the vote landed
|
||||
let vote_account = new_bank
|
||||
.get_vote_account(&keypairs.vote_keypair.pubkey())
|
||||
.unwrap();
|
||||
let state = vote_account.1.vote_state();
|
||||
assert!(state
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.votes
|
||||
.iter()
|
||||
.any(|lockout| lockout.slot == parent));
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
self.progress
|
||||
.get_fork_stats_mut(new_bank.slot())
|
||||
.expect("All frozen banks must exist in the Progress map")
|
||||
.bank_hash = Some(new_bank.hash());
|
||||
self.heaviest_subtree_fork_choice.add_new_leaf_slot(
|
||||
(new_bank.slot(), new_bank.hash()),
|
||||
Some((new_bank.parent_slot(), new_bank.parent_hash())),
|
||||
);
|
||||
self.bank_forks.write().unwrap().insert(new_bank);
|
||||
|
||||
walk.forward();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulate_vote(
|
||||
&mut self,
|
||||
vote_slot: Slot,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
) -> Vec<HeaviestForkFailures> {
|
||||
// Try to simulate the vote
|
||||
let my_keypairs = self.validator_keypairs.get(my_pubkey).unwrap();
|
||||
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
|
||||
let ancestors = self.bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.frozen_banks()
|
||||
.values()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let _ = ReplayStage::compute_bank_stats(
|
||||
my_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
tower,
|
||||
&mut self.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
&self.bank_forks,
|
||||
&mut self.heaviest_subtree_fork_choice,
|
||||
&mut self.latest_validator_votes_for_frozen_banks,
|
||||
);
|
||||
|
||||
let vote_bank = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(vote_slot)
|
||||
.expect("Bank must have been created before vote simulation")
|
||||
.clone();
|
||||
|
||||
// Try to vote on the given slot
|
||||
let descendants = self.bank_forks.read().unwrap().descendants().clone();
|
||||
let SelectVoteAndResetForkResult {
|
||||
heaviest_fork_failures,
|
||||
..
|
||||
} = ReplayStage::select_vote_and_reset_forks(
|
||||
&vote_bank,
|
||||
None,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&self.progress,
|
||||
tower,
|
||||
&self.latest_validator_votes_for_frozen_banks,
|
||||
&self.heaviest_subtree_fork_choice,
|
||||
);
|
||||
|
||||
// Make sure this slot isn't locked out or failing threshold
|
||||
info!("Checking vote: {}", vote_bank.slot());
|
||||
if !heaviest_fork_failures.is_empty() {
|
||||
return heaviest_fork_failures;
|
||||
}
|
||||
|
||||
let new_root = tower.record_bank_vote(&vote_bank, &my_vote_pubkey);
|
||||
if let Some(new_root) = new_root {
|
||||
self.set_root(new_root);
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub fn set_root(&mut self, new_root: Slot) {
|
||||
ReplayStage::handle_new_root(
|
||||
new_root,
|
||||
&self.bank_forks,
|
||||
&mut self.progress,
|
||||
&AbsRequestSender::default(),
|
||||
None,
|
||||
&mut self.heaviest_subtree_fork_choice,
|
||||
&mut DuplicateSlotsTracker::default(),
|
||||
&mut GossipDuplicateConfirmedSlots::default(),
|
||||
&mut UnfrozenGossipVerifiedVoteHashes::default(),
|
||||
&mut true,
|
||||
&mut Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_and_vote_new_branch(
|
||||
&mut self,
|
||||
start_slot: Slot,
|
||||
end_slot: Slot,
|
||||
cluster_votes: &HashMap<Pubkey, Vec<u64>>,
|
||||
votes_to_simulate: &HashSet<Slot>,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
) -> HashMap<Slot, Vec<HeaviestForkFailures>> {
|
||||
(start_slot + 1..=end_slot)
|
||||
.filter_map(|slot| {
|
||||
let mut fork_tip_parent = tr(slot - 1);
|
||||
fork_tip_parent.push_front(tr(slot));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if votes_to_simulate.contains(&slot) {
|
||||
Some((slot, self.simulate_vote(slot, my_pubkey, tower)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn simulate_lockout_interval(
|
||||
&mut self,
|
||||
slot: Slot,
|
||||
lockout_interval: (u64, u64),
|
||||
vote_account_pubkey: &Pubkey,
|
||||
) {
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0))
|
||||
.fork_stats
|
||||
.lockout_intervals
|
||||
.entry(lockout_interval.1)
|
||||
.or_default()
|
||||
.push((lockout_interval.0, *vote_account_pubkey));
|
||||
}
|
||||
|
||||
pub fn can_progress_on_fork(
|
||||
&mut self,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
|
||||
for i in 1..num_slots {
|
||||
// The parent of the tip of the fork
|
||||
let mut fork_tip_parent = tr(start_slot + i - 1);
|
||||
// The tip of the fork
|
||||
fork_tip_parent.push_front(tr(start_slot + i));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if self
|
||||
.simulate_vote(i + start_slot, my_pubkey, tower)
|
||||
.is_empty()
|
||||
{
|
||||
cluster_votes
|
||||
.entry(*my_pubkey)
|
||||
.or_default()
|
||||
.push(start_slot + i);
|
||||
}
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn init_state(
|
||||
num_keypairs: usize,
|
||||
) -> (
|
||||
HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
Vec<Pubkey>,
|
||||
Vec<Pubkey>,
|
||||
BankForks,
|
||||
ProgressMap,
|
||||
HeaviestSubtreeForkChoice,
|
||||
) {
|
||||
let keypairs: HashMap<_, _> = std::iter::repeat_with(|| {
|
||||
let vote_keypairs = ValidatorVoteKeypairs::new_rand();
|
||||
(vote_keypairs.node_keypair.pubkey(), vote_keypairs)
|
||||
})
|
||||
.take(num_keypairs)
|
||||
.collect();
|
||||
let node_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.node_keypair.pubkey())
|
||||
.collect();
|
||||
let vote_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.vote_keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
let (bank_forks, progress, heaviest_subtree_fork_choice) =
|
||||
initialize_state(&keypairs, 10_000);
|
||||
(
|
||||
keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks,
|
||||
progress,
|
||||
heaviest_subtree_fork_choice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup BankForks with bank 0 and all the validator accounts
|
||||
pub fn initialize_state(
|
||||
validator_keypairs_map: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
stake: u64,
|
||||
) -> (BankForks, ProgressMap, HeaviestSubtreeForkChoice) {
|
||||
let validator_keypairs: Vec<_> = validator_keypairs_map.values().collect();
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
voting_keypair: _,
|
||||
} = create_genesis_config_with_vote_accounts(
|
||||
1_000_000_000,
|
||||
&validator_keypairs,
|
||||
vec![stake; validator_keypairs.len()],
|
||||
);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
for pubkey in validator_keypairs_map.keys() {
|
||||
bank0.transfer(10_000, &mint_keypair, pubkey).unwrap();
|
||||
}
|
||||
|
||||
bank0.freeze();
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new_from_bank(&bank0, bank0.collector_id(), &Pubkey::default(), None, 0, 0),
|
||||
);
|
||||
let bank_forks = BankForks::new(bank0);
|
||||
let heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new_from_bank_forks(&bank_forks);
|
||||
(bank_forks, progress, heaviest_subtree_fork_choice)
|
||||
}
|
|
@ -90,6 +90,10 @@ impl BankForks {
|
|||
maybe_bank
|
||||
}
|
||||
|
||||
pub fn bank_hash(&self, slot: Slot) -> Option<Hash> {
|
||||
self.get(slot).map(|bank| bank.hash())
|
||||
}
|
||||
|
||||
pub fn root_bank(&self) -> Arc<Bank> {
|
||||
self[self.root()].clone()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue