Setup ReplayStage confirmation scaffolding for duplicate slots (#9698)
This commit is contained in:
@@ -37,6 +37,7 @@ pub enum SwitchForkDecision {
|
||||
SwitchProof(Hash),
|
||||
SameFork,
|
||||
FailedSwitchThreshold(u64, u64),
|
||||
FailedSwitchDuplicateRollback(Slot),
|
||||
}
|
||||
|
||||
impl SwitchForkDecision {
|
||||
@@ -51,6 +52,7 @@ impl SwitchForkDecision {
|
||||
assert_ne!(*total_stake, 0);
|
||||
None
|
||||
}
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(_) => None,
|
||||
SwitchForkDecision::SameFork => Some(vote_instruction::vote(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_pubkey,
|
||||
@@ -68,7 +70,12 @@ impl SwitchForkDecision {
|
||||
}
|
||||
|
||||
pub fn can_vote(&self) -> bool {
|
||||
!matches!(self, SwitchForkDecision::FailedSwitchThreshold(_, _))
|
||||
match self {
|
||||
SwitchForkDecision::FailedSwitchThreshold(_, _) => false,
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(_) => false,
|
||||
SwitchForkDecision::SameFork => true,
|
||||
SwitchForkDecision::SwitchProof(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +390,17 @@ impl Tower {
|
||||
slot
|
||||
}
|
||||
|
||||
pub fn record_bank_vote(
|
||||
&mut self,
|
||||
bank: &Bank,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
) -> (Option<Slot>, Vec<Slot> /*VoteState.tower*/) {
|
||||
let (vote, tower_slots) = self.new_vote_from_bank(bank, vote_account_pubkey);
|
||||
|
||||
let new_root = self.record_bank_vote_update_lockouts(vote);
|
||||
(new_root, tower_slots)
|
||||
}
|
||||
|
||||
pub fn new_vote_from_bank(
|
||||
&self,
|
||||
bank: &Bank,
|
||||
@@ -392,7 +410,7 @@ impl Tower {
|
||||
Self::new_vote(&self.lockouts, bank.slot(), bank.hash(), voted_slot)
|
||||
}
|
||||
|
||||
pub fn record_bank_vote(&mut self, vote: Vote) -> Option<Slot> {
|
||||
pub fn record_bank_vote_update_lockouts(&mut self, vote: Vote) -> Option<Slot> {
|
||||
let slot = vote.last_voted_slot().unwrap_or(0);
|
||||
trace!("{} record_vote for {}", self.node_pubkey, slot);
|
||||
let old_root = self.root();
|
||||
@@ -411,7 +429,7 @@ impl Tower {
|
||||
#[cfg(test)]
|
||||
pub fn record_vote(&mut self, slot: Slot, hash: Hash) -> Option<Slot> {
|
||||
let vote = Vote::new(vec![slot], hash);
|
||||
self.record_bank_vote(vote)
|
||||
self.record_bank_vote_update_lockouts(vote)
|
||||
}
|
||||
|
||||
pub fn last_voted_slot(&self) -> Option<Slot> {
|
||||
@@ -575,9 +593,13 @@ impl Tower {
|
||||
SwitchForkDecision::FailedSwitchThreshold(0, total_stake)
|
||||
};
|
||||
|
||||
let rollback_due_to_to_to_duplicate_ancestor = |latest_duplicate_ancestor| {
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(latest_duplicate_ancestor)
|
||||
};
|
||||
|
||||
let last_vote_ancestors =
|
||||
ancestors.get(&last_voted_slot).unwrap_or_else(|| {
|
||||
if !self.is_stray_last_vote() {
|
||||
if self.is_stray_last_vote() {
|
||||
// Unless last vote is stray and stale, ancestors.get(last_voted_slot) must
|
||||
// return Some(_), justifying to panic! here.
|
||||
// Also, adjust_lockouts_after_replay() correctly makes last_voted_slot None,
|
||||
@@ -586,9 +608,9 @@ impl Tower {
|
||||
// In other words, except being stray, all other slots have been voted on while
|
||||
// this validator has been running, so we must be able to fetch ancestors for
|
||||
// all of them.
|
||||
panic!("no ancestors found with slot: {}", last_voted_slot);
|
||||
} else {
|
||||
empty_ancestors_due_to_minor_unsynced_ledger()
|
||||
} else {
|
||||
panic!("no ancestors found with slot: {}", last_voted_slot);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -601,15 +623,23 @@ impl Tower {
|
||||
}
|
||||
|
||||
if last_vote_ancestors.contains(&switch_slot) {
|
||||
if !self.is_stray_last_vote() {
|
||||
panic!(
|
||||
"Should never consider switching to slot ({}), which is ancestors({:?}) of last vote: {}",
|
||||
switch_slot,
|
||||
last_vote_ancestors,
|
||||
last_voted_slot
|
||||
);
|
||||
} else {
|
||||
if self.is_stray_last_vote() {
|
||||
return suspended_decision_due_to_major_unsynced_ledger();
|
||||
} else if let Some(latest_duplicate_ancestor) = progress.latest_unconfirmed_duplicate_ancestor(last_voted_slot) {
|
||||
// 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({:?})",
|
||||
switch_slot,
|
||||
last_voted_slot,
|
||||
last_vote_ancestors,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1240,7 +1270,7 @@ pub mod test {
|
||||
cluster_slots::ClusterSlots,
|
||||
fork_choice::SelectVoteAndResetForkResult,
|
||||
heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice,
|
||||
progress_map::ForkProgress,
|
||||
progress_map::{DuplicateStats, ForkProgress},
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
};
|
||||
use solana_ledger::{blockstore::make_slot_entries, get_tmp_ledger_path};
|
||||
@@ -1265,7 +1295,7 @@ pub mod test {
|
||||
vote_transaction,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{BTreeMap, HashMap},
|
||||
fs::{remove_file, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
sync::RwLock,
|
||||
@@ -1313,9 +1343,9 @@ pub mod test {
|
||||
|
||||
while let Some(visit) = walk.get() {
|
||||
let slot = visit.node().data;
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0));
|
||||
self.progress.entry(slot).or_insert_with(|| {
|
||||
ForkProgress::new(Hash::default(), None, DuplicateStats::default(), None, 0, 0)
|
||||
});
|
||||
if self.bank_forks.read().unwrap().get(slot).is_some() {
|
||||
walk.forward();
|
||||
continue;
|
||||
@@ -1395,7 +1425,7 @@ pub mod test {
|
||||
..
|
||||
} = ReplayStage::select_vote_and_reset_forks(
|
||||
&vote_bank,
|
||||
&None,
|
||||
None,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&self.progress,
|
||||
@@ -1407,8 +1437,9 @@ pub mod test {
|
||||
if !heaviest_fork_failures.is_empty() {
|
||||
return heaviest_fork_failures;
|
||||
}
|
||||
let vote = tower.new_vote_from_bank(&vote_bank, &my_vote_pubkey).0;
|
||||
if let Some(new_root) = tower.record_bank_vote(vote) {
|
||||
|
||||
let (new_root, _) = tower.record_bank_vote(&vote_bank, &my_vote_pubkey);
|
||||
if let Some(new_root) = new_root {
|
||||
self.set_root(new_root);
|
||||
}
|
||||
|
||||
@@ -1423,6 +1454,7 @@ pub mod test {
|
||||
&AbsRequestSender::default(),
|
||||
None,
|
||||
&mut self.heaviest_subtree_fork_choice,
|
||||
&mut BTreeMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1457,7 +1489,9 @@ pub mod test {
|
||||
) {
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0))
|
||||
.or_insert_with(|| {
|
||||
ForkProgress::new(Hash::default(), None, DuplicateStats::default(), None, 0, 0)
|
||||
})
|
||||
.fork_stats
|
||||
.lockout_intervals
|
||||
.entry(lockout_interval.1)
|
||||
@@ -1564,7 +1598,14 @@ pub mod test {
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new(bank0.last_blockhash(), None, None, 0, 0),
|
||||
ForkProgress::new(
|
||||
bank0.last_blockhash(),
|
||||
None,
|
||||
DuplicateStats::default(),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
let bank_forks = BankForks::new(bank0);
|
||||
let heaviest_subtree_fork_choice =
|
||||
@@ -1604,6 +1645,12 @@ pub mod test {
|
||||
assert!(decision
|
||||
.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default())
|
||||
.is_none());
|
||||
|
||||
decision = SwitchForkDecision::FailedSwitchDuplicateRollback(0);
|
||||
assert!(decision
|
||||
.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default())
|
||||
.is_none());
|
||||
|
||||
decision = SwitchForkDecision::SameFork;
|
||||
assert_eq!(
|
||||
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||
@@ -1613,6 +1660,7 @@ pub mod test {
|
||||
vote.clone(),
|
||||
))
|
||||
);
|
||||
|
||||
decision = SwitchForkDecision::SwitchProof(Hash::default());
|
||||
assert_eq!(
|
||||
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||
@@ -1655,11 +1703,20 @@ pub mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_threshold() {
|
||||
fn test_switch_threshold_duplicate_rollback() {
|
||||
run_test_switch_threshold_duplicate_rollback(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_switch_threshold_duplicate_rollback_panic() {
|
||||
run_test_switch_threshold_duplicate_rollback(true);
|
||||
}
|
||||
|
||||
fn setup_switch_test(num_accounts: usize) -> (Arc<Bank>, VoteSimulator, u64) {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(2);
|
||||
let my_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let other_vote_account = vote_simulator.vote_pubkeys[1];
|
||||
assert!(num_accounts > 1);
|
||||
let mut vote_simulator = VoteSimulator::new(num_accounts);
|
||||
let bank0 = vote_simulator
|
||||
.bank_forks
|
||||
.read()
|
||||
@@ -1690,6 +1747,82 @@ pub mod test {
|
||||
for (_, fork_progress) in vote_simulator.progress.iter_mut() {
|
||||
fork_progress.fork_stats.computed = true;
|
||||
}
|
||||
|
||||
(bank0, vote_simulator, total_stake)
|
||||
}
|
||||
|
||||
fn run_test_switch_threshold_duplicate_rollback(should_panic: bool) {
|
||||
let (bank0, mut vote_simulator, total_stake) = setup_switch_test(2);
|
||||
let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
|
||||
let descendants = vote_simulator
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.descendants()
|
||||
.clone();
|
||||
let mut tower = Tower::new_with_key(&vote_simulator.node_pubkeys[0]);
|
||||
|
||||
// Last vote is 47
|
||||
tower.record_vote(47, Hash::default());
|
||||
|
||||
// Trying to switch to an ancestor of last vote should only not panic
|
||||
// if the current vote has a duplicate ancestor
|
||||
let ancestor_of_voted_slot = 43;
|
||||
let duplicate_ancestor1 = 44;
|
||||
let duplicate_ancestor2 = 45;
|
||||
vote_simulator.progress.set_unconfirmed_duplicate_slot(
|
||||
duplicate_ancestor1,
|
||||
&descendants.get(&duplicate_ancestor1).unwrap(),
|
||||
);
|
||||
vote_simulator.progress.set_unconfirmed_duplicate_slot(
|
||||
duplicate_ancestor2,
|
||||
&descendants.get(&duplicate_ancestor2).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
tower.check_switch_threshold(
|
||||
ancestor_of_voted_slot,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&vote_simulator.progress,
|
||||
total_stake,
|
||||
bank0.epoch_vote_accounts(0).unwrap(),
|
||||
),
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(duplicate_ancestor2)
|
||||
);
|
||||
let mut confirm_ancestors = vec![duplicate_ancestor1];
|
||||
if should_panic {
|
||||
// Adding the last duplicate ancestor will
|
||||
// 1) Cause loop below to confirm last ancestor
|
||||
// 2) Check switch threshold on a vote ancestor when there
|
||||
// are no duplicates on that fork, which will cause a panic
|
||||
confirm_ancestors.push(duplicate_ancestor2);
|
||||
}
|
||||
for (i, duplicate_ancestor) in confirm_ancestors.into_iter().enumerate() {
|
||||
vote_simulator.progress.set_confirmed_duplicate_slot(
|
||||
duplicate_ancestor,
|
||||
ancestors.get(&duplicate_ancestor).unwrap(),
|
||||
&descendants.get(&duplicate_ancestor).unwrap(),
|
||||
);
|
||||
let res = tower.check_switch_threshold(
|
||||
ancestor_of_voted_slot,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&vote_simulator.progress,
|
||||
total_stake,
|
||||
bank0.epoch_vote_accounts(0).unwrap(),
|
||||
);
|
||||
if i == 0 {
|
||||
assert_eq!(
|
||||
res,
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(duplicate_ancestor2)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_threshold() {
|
||||
let (bank0, mut vote_simulator, total_stake) = setup_switch_test(2);
|
||||
let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
|
||||
let mut descendants = vote_simulator
|
||||
.bank_forks
|
||||
@@ -1697,7 +1830,8 @@ pub mod test {
|
||||
.unwrap()
|
||||
.descendants()
|
||||
.clone();
|
||||
let mut tower = Tower::new_with_key(&my_pubkey);
|
||||
let mut tower = Tower::new_with_key(&vote_simulator.node_pubkeys[0]);
|
||||
let other_vote_account = vote_simulator.vote_pubkeys[1];
|
||||
|
||||
// Last vote is 47
|
||||
tower.record_vote(47, Hash::default());
|
||||
|
||||
Reference in New Issue
Block a user