Retry latest vote if expired (#16735)
This commit is contained in:
parent
9070191b8b
commit
b5d30846d6
|
@ -1363,7 +1363,7 @@ impl BankingStage {
|
|||
|
||||
pub(crate) fn next_leader_tpu(
|
||||
cluster_info: &ClusterInfo,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
poh_recorder: &Mutex<PohRecorder>,
|
||||
) -> Option<std::net::SocketAddr> {
|
||||
if let Some(leader_pubkey) = poh_recorder
|
||||
.lock()
|
||||
|
|
|
@ -1019,9 +1019,21 @@ impl ClusterInfo {
|
|||
self.push_message(CrdsValue::new_signed(message, &self.keypair));
|
||||
}
|
||||
|
||||
fn push_vote_at_index(&self, vote: Transaction, vote_index: u8) {
|
||||
assert!((vote_index as usize) < MAX_LOCKOUT_HISTORY);
|
||||
let self_pubkey = self.id();
|
||||
let now = timestamp();
|
||||
let vote = Vote::new(self_pubkey, vote, now);
|
||||
let vote = CrdsData::Vote(vote_index, vote);
|
||||
let vote = CrdsValue::new_signed(vote, &self.keypair);
|
||||
self.gossip
|
||||
.write()
|
||||
.unwrap()
|
||||
.process_push_message(&self_pubkey, vec![vote], now);
|
||||
}
|
||||
|
||||
pub fn push_vote(&self, tower: &[Slot], vote: Transaction) {
|
||||
debug_assert!(tower.iter().tuple_windows().all(|(a, b)| a < b));
|
||||
let now = timestamp();
|
||||
// Find a crds vote which is evicted from the tower, and recycle its
|
||||
// vote-index. This can be either an old vote which is popped off the
|
||||
// deque, or recent vote which has expired before getting enough
|
||||
|
@ -1064,15 +1076,40 @@ impl ClusterInfo {
|
|||
.map(|(_ /*wallclock*/, ix)| ix)
|
||||
};
|
||||
let vote_index = vote_index.unwrap_or(num_crds_votes);
|
||||
assert!((vote_index as usize) < MAX_LOCKOUT_HISTORY);
|
||||
let vote = Vote::new(self_pubkey, vote, now);
|
||||
debug_assert_eq!(vote.slot().unwrap(), *tower.last().unwrap());
|
||||
let vote = CrdsData::Vote(vote_index, vote);
|
||||
let vote = CrdsValue::new_signed(vote, &self.keypair);
|
||||
self.gossip
|
||||
.write()
|
||||
.unwrap()
|
||||
.process_push_message(&self_pubkey, vec![vote], now);
|
||||
self.push_vote_at_index(vote, vote_index);
|
||||
}
|
||||
|
||||
pub fn refresh_vote(&self, vote: Transaction, vote_slot: Slot) {
|
||||
let vote_index = {
|
||||
let gossip =
|
||||
self.time_gossip_read_lock("gossip_read_push_vote", &self.stats.push_vote_read);
|
||||
(0..MAX_LOCKOUT_HISTORY as u8).find(|ix| {
|
||||
let vote = CrdsValueLabel::Vote(*ix, self.id());
|
||||
if let Some(vote) = gossip.crds.lookup(&vote) {
|
||||
match &vote.data {
|
||||
CrdsData::Vote(_, prev_vote) => match prev_vote.slot() {
|
||||
Some(prev_vote_slot) => prev_vote_slot == vote_slot,
|
||||
None => {
|
||||
error!("crds vote with no slots!");
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => panic!("this should not happen!"),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// If you don't see a vote with the same slot yet, this means you probably
|
||||
// restarted, and need to wait for your oldest vote to propagate back to you.
|
||||
//
|
||||
// We don't write to an arbitrary index, because it may replace one of this validator's
|
||||
// existing votes on the network.
|
||||
if let Some(vote_index) = vote_index {
|
||||
self.push_vote_at_index(vote, vote_index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_vote(&self, vote: &Transaction, tpu: Option<SocketAddr>) -> Result<()> {
|
||||
|
@ -3570,6 +3607,96 @@ mod tests {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refresh_vote() {
|
||||
let keys = Keypair::new();
|
||||
let contact_info = ContactInfo::new_localhost(&keys.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
|
||||
// Construct and push a vote for some other slot
|
||||
let unrefresh_slot = 5;
|
||||
let unrefresh_tower = vec![1, 3, unrefresh_slot];
|
||||
let unrefresh_vote = Vote::new(unrefresh_tower.clone(), Hash::new_unique());
|
||||
let unrefresh_ix = vote_instruction::vote(
|
||||
&Pubkey::new_unique(), // vote_pubkey
|
||||
&Pubkey::new_unique(), // authorized_voter_pubkey
|
||||
unrefresh_vote,
|
||||
);
|
||||
let unrefresh_tx = Transaction::new_with_payer(
|
||||
&[unrefresh_ix], // instructions
|
||||
None, // payer
|
||||
);
|
||||
cluster_info.push_vote(&unrefresh_tower, unrefresh_tx.clone());
|
||||
cluster_info.flush_push_queue();
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(0);
|
||||
assert_eq!(votes, vec![unrefresh_tx.clone()]);
|
||||
|
||||
// Now construct vote for the slot to be refreshed later
|
||||
let refresh_slot = 7;
|
||||
let refresh_tower = vec![1, 3, unrefresh_slot, refresh_slot];
|
||||
let refresh_vote = Vote::new(refresh_tower.clone(), Hash::new_unique());
|
||||
let refresh_ix = vote_instruction::vote(
|
||||
&Pubkey::new_unique(), // vote_pubkey
|
||||
&Pubkey::new_unique(), // authorized_voter_pubkey
|
||||
refresh_vote.clone(),
|
||||
);
|
||||
let refresh_tx = Transaction::new_with_payer(
|
||||
&[refresh_ix], // instructions
|
||||
None, // payer
|
||||
);
|
||||
|
||||
// Trying to refresh vote when it doesn't yet exist in gossip
|
||||
// shouldn't add the vote
|
||||
cluster_info.refresh_vote(refresh_tx.clone(), refresh_slot);
|
||||
cluster_info.flush_push_queue();
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert_eq!(votes, vec![]);
|
||||
let (_, votes, _) = cluster_info.get_votes(0);
|
||||
assert_eq!(votes.len(), 1);
|
||||
assert!(votes.contains(&unrefresh_tx));
|
||||
|
||||
// Push the new vote for `refresh_slot`
|
||||
cluster_info.push_vote(&refresh_tower, refresh_tx.clone());
|
||||
cluster_info.flush_push_queue();
|
||||
|
||||
// Should be two votes in gossip
|
||||
let (_, votes, _) = cluster_info.get_votes(0);
|
||||
assert_eq!(votes.len(), 2);
|
||||
assert!(votes.contains(&unrefresh_tx));
|
||||
assert!(votes.contains(&refresh_tx));
|
||||
|
||||
// Refresh a few times, we should only have the latest update
|
||||
let mut latest_refresh_tx = refresh_tx;
|
||||
for _ in 0..10 {
|
||||
let latest_refreshed_recent_blockhash = Hash::new_unique();
|
||||
let new_signer = Keypair::new();
|
||||
let refresh_ix = vote_instruction::vote(
|
||||
&new_signer.pubkey(), // vote_pubkey
|
||||
&new_signer.pubkey(), // authorized_voter_pubkey
|
||||
refresh_vote.clone(),
|
||||
);
|
||||
latest_refresh_tx = Transaction::new_signed_with_payer(
|
||||
&[refresh_ix],
|
||||
None,
|
||||
&[&new_signer],
|
||||
latest_refreshed_recent_blockhash,
|
||||
);
|
||||
cluster_info.refresh_vote(latest_refresh_tx.clone(), refresh_slot);
|
||||
}
|
||||
cluster_info.flush_push_queue();
|
||||
|
||||
// The diff since `max_ts` should only be the latest refreshed vote
|
||||
let (_, votes, _) = cluster_info.get_votes(max_ts);
|
||||
assert_eq!(votes.len(), 1);
|
||||
assert_eq!(votes[0], latest_refresh_tx);
|
||||
|
||||
// Should still be two votes in gossip
|
||||
let (_, votes, _) = cluster_info.get_votes(0);
|
||||
assert_eq!(votes.len(), 2);
|
||||
assert!(votes.contains(&unrefresh_tx));
|
||||
assert!(votes.contains(&latest_refresh_tx));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_vote() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
|
|
@ -98,6 +98,7 @@ pub(crate) struct ComputedBankState {
|
|||
// Tree of intervals of lockouts of the form [slot, slot + slot.lockout],
|
||||
// keyed by end of the range
|
||||
pub lockout_intervals: LockoutIntervals,
|
||||
pub my_latest_landed_vote: Option<Slot>,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "Eay84NBbJqiMBfE7HHH2o6e51wcvoU79g8zCi5sw6uj3")]
|
||||
|
@ -108,6 +109,12 @@ pub struct Tower {
|
|||
threshold_size: f64,
|
||||
lockouts: VoteState,
|
||||
last_vote: Vote,
|
||||
#[serde(skip)]
|
||||
// The blockhash used in the last vote transaction, may or may not equal the
|
||||
// blockhash of the voted block itself, depending if the vote slot was refreshed.
|
||||
// For instance, a vote for slot 5, may be refreshed/resubmitted for inclusion in
|
||||
// block 10, in which case `last_vote_tx_blockhash` equals the blockhash of 10, not 5.
|
||||
last_vote_tx_blockhash: Hash,
|
||||
last_timestamp: BlockTimestamp,
|
||||
#[serde(skip)]
|
||||
path: PathBuf,
|
||||
|
@ -134,6 +141,7 @@ impl Default for Tower {
|
|||
lockouts: VoteState::default(),
|
||||
last_vote: Vote::default(),
|
||||
last_timestamp: BlockTimestamp::default(),
|
||||
last_vote_tx_blockhash: Hash::default(),
|
||||
path: PathBuf::default(),
|
||||
tmp_path: PathBuf::default(),
|
||||
stray_restored_slot: Option::default(),
|
||||
|
@ -217,7 +225,7 @@ impl Tower {
|
|||
}
|
||||
|
||||
pub(crate) fn collect_vote_lockouts<F>(
|
||||
node_pubkey: &Pubkey,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
bank_slot: Slot,
|
||||
vote_accounts: F,
|
||||
ancestors: &HashMap<Slot, HashSet<Slot>>,
|
||||
|
@ -234,11 +242,12 @@ impl Tower {
|
|||
// Tree of intervals of lockouts of the form [slot, slot + slot.lockout],
|
||||
// keyed by end of the range
|
||||
let mut lockout_intervals = LockoutIntervals::new();
|
||||
let mut my_latest_landed_vote = None;
|
||||
for (key, (voted_stake, account)) in vote_accounts {
|
||||
if voted_stake == 0 {
|
||||
continue;
|
||||
}
|
||||
trace!("{} {} with stake {}", node_pubkey, key, voted_stake);
|
||||
trace!("{} {} with stake {}", vote_account_pubkey, key, voted_stake);
|
||||
let mut vote_state = match account.vote_state().as_ref() {
|
||||
Err(_) => {
|
||||
datapoint_warn!(
|
||||
|
@ -260,7 +269,8 @@ impl Tower {
|
|||
.push((vote.slot, key));
|
||||
}
|
||||
|
||||
if key == *node_pubkey || vote_state.node_pubkey == *node_pubkey {
|
||||
if key == *vote_account_pubkey {
|
||||
my_latest_landed_vote = vote_state.nth_recent_vote(0).map(|v| v.slot);
|
||||
debug!("vote state {:?}", vote_state);
|
||||
debug!(
|
||||
"observed slot {}",
|
||||
|
@ -348,6 +358,7 @@ impl Tower {
|
|||
total_stake,
|
||||
bank_weight,
|
||||
lockout_intervals,
|
||||
my_latest_landed_vote,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,13 +374,24 @@ impl Tower {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn new_vote(
|
||||
local_vote_state: &VoteState,
|
||||
pub fn tower_slots(&self) -> Vec<Slot> {
|
||||
self.lockouts.tower()
|
||||
}
|
||||
|
||||
pub fn last_vote_tx_blockhash(&self) -> Hash {
|
||||
self.last_vote_tx_blockhash
|
||||
}
|
||||
|
||||
pub fn refresh_last_vote_tx_blockhash(&mut self, new_vote_tx_blockhash: Hash) {
|
||||
self.last_vote_tx_blockhash = new_vote_tx_blockhash;
|
||||
}
|
||||
|
||||
fn apply_vote_and_generate_vote_diff(
|
||||
local_vote_state: &mut VoteState,
|
||||
slot: Slot,
|
||||
hash: Hash,
|
||||
last_voted_slot_in_bank: Option<Slot>,
|
||||
) -> (Vote, Vec<Slot> /*VoteState.tower*/) {
|
||||
let mut local_vote_state = local_vote_state.clone();
|
||||
) -> Vote {
|
||||
let vote = Vote::new(vec![slot], hash);
|
||||
local_vote_state.process_vote_unchecked(&vote);
|
||||
let slots = if let Some(last_voted_slot_in_bank) = last_voted_slot_in_bank {
|
||||
|
@ -388,44 +410,48 @@ impl Tower {
|
|||
slots,
|
||||
local_vote_state.votes
|
||||
);
|
||||
(Vote::new(slots, hash), local_vote_state.tower())
|
||||
Vote::new(slots, hash)
|
||||
}
|
||||
|
||||
fn last_voted_slot_in_bank(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
|
||||
pub fn last_voted_slot_in_bank(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
|
||||
let (_stake, vote_account) = bank.get_vote_account(vote_account_pubkey)?;
|
||||
let slot = vote_account.vote_state().as_ref().ok()?.last_voted_slot();
|
||||
slot
|
||||
}
|
||||
|
||||
pub fn record_bank_vote(
|
||||
pub fn record_bank_vote(&mut self, bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
|
||||
let last_voted_slot_in_bank = Self::last_voted_slot_in_bank(bank, vote_account_pubkey);
|
||||
|
||||
// Returns the new root if one is made after applying a vote for the given bank to
|
||||
// `self.lockouts`
|
||||
self.record_bank_vote_and_update_lockouts(bank.slot(), bank.hash(), last_voted_slot_in_bank)
|
||||
}
|
||||
|
||||
fn record_bank_vote_and_update_lockouts(
|
||||
&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,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
) -> (Vote, Vec<Slot> /*VoteState.tower*/) {
|
||||
let voted_slot = Self::last_voted_slot_in_bank(bank, vote_account_pubkey);
|
||||
Self::new_vote(&self.lockouts, bank.slot(), bank.hash(), voted_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);
|
||||
vote_slot: Slot,
|
||||
vote_hash: Hash,
|
||||
last_voted_slot_in_bank: Option<Slot>,
|
||||
) -> Option<Slot> {
|
||||
trace!("{} record_vote for {}", self.node_pubkey, vote_slot);
|
||||
let old_root = self.root();
|
||||
self.lockouts.process_vote_unchecked(&vote);
|
||||
self.last_vote = vote;
|
||||
let mut new_vote = Self::apply_vote_and_generate_vote_diff(
|
||||
&mut self.lockouts,
|
||||
vote_slot,
|
||||
vote_hash,
|
||||
last_voted_slot_in_bank,
|
||||
);
|
||||
|
||||
new_vote.timestamp = self.maybe_timestamp(self.last_vote.last_voted_slot().unwrap_or(0));
|
||||
self.last_vote = new_vote;
|
||||
|
||||
let new_root = self.root();
|
||||
|
||||
datapoint_info!("tower-vote", ("latest", slot, i64), ("root", new_root, i64));
|
||||
datapoint_info!(
|
||||
"tower-vote",
|
||||
("latest", vote_slot, i64),
|
||||
("root", new_root, i64)
|
||||
);
|
||||
if old_root != new_root {
|
||||
Some(new_root)
|
||||
} else {
|
||||
|
@ -435,8 +461,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_update_lockouts(vote)
|
||||
self.record_bank_vote_and_update_lockouts(slot, hash, self.last_voted_slot())
|
||||
}
|
||||
|
||||
pub fn last_voted_slot(&self) -> Option<Slot> {
|
||||
|
@ -451,10 +476,8 @@ impl Tower {
|
|||
self.stray_restored_slot
|
||||
}
|
||||
|
||||
pub fn last_vote_and_timestamp(&mut self) -> Vote {
|
||||
let mut last_vote = self.last_vote.clone();
|
||||
last_vote.timestamp = self.maybe_timestamp(last_vote.last_voted_slot().unwrap_or(0));
|
||||
last_vote
|
||||
pub fn last_vote(&mut self) -> Vote {
|
||||
self.last_vote.clone()
|
||||
}
|
||||
|
||||
fn maybe_timestamp(&mut self, current_slot: Slot) -> Option<UnixTimestamp> {
|
||||
|
@ -1457,7 +1480,7 @@ pub mod test {
|
|||
return heaviest_fork_failures;
|
||||
}
|
||||
|
||||
let (new_root, _) = tower.record_bank_vote(&vote_bank, &my_vote_pubkey);
|
||||
let new_root = tower.record_bank_vote(&vote_bank, &my_vote_pubkey);
|
||||
if let Some(new_root) = new_root {
|
||||
self.set_root(new_root);
|
||||
}
|
||||
|
@ -2442,23 +2465,26 @@ pub mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_vote() {
|
||||
let local = VoteState::default();
|
||||
let (vote, tower_slots) = Tower::new_vote(&local, 0, Hash::default(), None);
|
||||
assert_eq!(local.votes.len(), 0);
|
||||
fn test_apply_vote_and_generate_vote_diff() {
|
||||
let mut local = VoteState::default();
|
||||
let vote = Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), None);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
assert_eq!(vote.slots, vec![0]);
|
||||
assert_eq!(tower_slots, vec![0]);
|
||||
assert_eq!(local.tower(), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_vote_dup_vote() {
|
||||
let local = VoteState::default();
|
||||
let vote = Tower::new_vote(&local, 0, Hash::default(), Some(0));
|
||||
assert!(vote.0.slots.is_empty());
|
||||
fn test_apply_vote_and_generate_vote_diff_dup_vote() {
|
||||
let mut local = VoteState::default();
|
||||
// If `latest_voted_slot_in_bank == Some(0)`, then we already have a vote for 0. Adding
|
||||
// another vote for slot 0 should return an empty vote as the diff.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), Some(0));
|
||||
assert!(vote.slots.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_vote_next_vote() {
|
||||
fn test_apply_vote_and_generate_vote_diff_next_vote() {
|
||||
let mut local = VoteState::default();
|
||||
let vote = Vote {
|
||||
slots: vec![0],
|
||||
|
@ -2467,13 +2493,14 @@ pub mod test {
|
|||
};
|
||||
local.process_vote_unchecked(&vote);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
let (vote, tower_slots) = Tower::new_vote(&local, 1, Hash::default(), Some(0));
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 1, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![1]);
|
||||
assert_eq!(tower_slots, vec![0, 1]);
|
||||
assert_eq!(local.tower(), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_vote_next_after_expired_vote() {
|
||||
fn test_apply_vote_and_generate_vote_diff_next_after_expired_vote() {
|
||||
let mut local = VoteState::default();
|
||||
let vote = Vote {
|
||||
slots: vec![0],
|
||||
|
@ -2482,10 +2509,14 @@ pub mod test {
|
|||
};
|
||||
local.process_vote_unchecked(&vote);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
let (vote, tower_slots) = Tower::new_vote(&local, 3, Hash::default(), Some(0));
|
||||
|
||||
// First vote expired, so should be evicted from tower. Thus even with
|
||||
// `latest_voted_slot_in_bank == Some(0)`, the first vote slot won't be
|
||||
// observable in any of the results.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 3, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![3]);
|
||||
// First vote expired, so should be evicted from tower.
|
||||
assert_eq!(tower_slots, vec![3]);
|
||||
assert_eq!(local.tower(), vec![3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2561,10 +2592,12 @@ pub mod test {
|
|||
} else {
|
||||
vec![]
|
||||
};
|
||||
let expected = Vote::new(slots, Hash::default());
|
||||
let mut expected = Vote::new(slots, Hash::default());
|
||||
for i in 0..num_votes {
|
||||
tower.record_vote(i as u64, Hash::default());
|
||||
}
|
||||
|
||||
expected.timestamp = tower.last_vote.timestamp;
|
||||
assert_eq!(expected, tower.last_vote)
|
||||
}
|
||||
|
||||
|
|
|
@ -259,6 +259,7 @@ pub(crate) struct ForkStats {
|
|||
pub(crate) computed: bool,
|
||||
pub(crate) lockout_intervals: LockoutIntervals,
|
||||
pub(crate) bank_hash: Option<Hash>,
|
||||
pub(crate) my_latest_landed_vote: Option<Slot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -532,6 +533,12 @@ impl ProgressMap {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn my_latest_landed_vote(&self, slot: Slot) -> Option<Slot> {
|
||||
self.progress_map
|
||||
.get(&slot)
|
||||
.and_then(|s| s.fork_stats.my_latest_landed_vote)
|
||||
}
|
||||
|
||||
pub fn set_supermajority_confirmed_slot(&mut self, slot: Slot) {
|
||||
let slot_progress = self.get_mut(&slot).unwrap();
|
||||
slot_progress.fork_stats.is_supermajority_confirmed = true;
|
||||
|
|
|
@ -41,7 +41,7 @@ use solana_runtime::{
|
|||
commitment::BlockCommitmentCache, vote_sender_types::ReplayVoteSender,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{Slot, NUM_CONSECUTIVE_LEADER_SLOTS},
|
||||
clock::{Slot, MAX_PROCESSING_AGE, NUM_CONSECUTIVE_LEADER_SLOTS},
|
||||
genesis_config::ClusterType,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
|
@ -50,7 +50,7 @@ use solana_sdk::{
|
|||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{vote_instruction, vote_state::Vote};
|
||||
use solana_vote_program::vote_state::Vote;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
result,
|
||||
|
@ -60,7 +60,7 @@ use std::{
|
|||
Arc, Mutex, RwLock,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub const MAX_ENTRY_RECV_PER_ITER: usize = 512;
|
||||
|
@ -69,6 +69,7 @@ pub const MAX_UNCONFIRMED_SLOTS: usize = 5;
|
|||
pub const DUPLICATE_LIVENESS_THRESHOLD: f64 = 0.1;
|
||||
pub const DUPLICATE_THRESHOLD: f64 = 1.0 - SWITCH_FORK_THRESHOLD - DUPLICATE_LIVENESS_THRESHOLD;
|
||||
const MAX_VOTE_SIGNATURES: usize = 200;
|
||||
const MAX_VOTE_REFRESH_INTERVAL_MILLIS: usize = 5000;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(crate) enum HeaviestForkFailures {
|
||||
|
@ -97,6 +98,11 @@ impl Drop for Finalizer {
|
|||
}
|
||||
}
|
||||
|
||||
struct LastVoteRefreshTime {
|
||||
last_refresh_time: Instant,
|
||||
last_print_time: Instant,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SkippedSlotsInfo {
|
||||
last_retransmit_slot: u64,
|
||||
|
@ -334,6 +340,10 @@ impl ReplayStage {
|
|||
let mut latest_validator_votes_for_frozen_banks: LatestValidatorVotesForFrozenBanks = LatestValidatorVotesForFrozenBanks::default();
|
||||
let mut voted_signatures = Vec::new();
|
||||
let mut has_new_vote_been_rooted = !wait_for_vote_to_start_leader;
|
||||
let mut last_vote_refresh_time = LastVoteRefreshTime {
|
||||
last_refresh_time: Instant::now(),
|
||||
last_print_time: Instant::now(),
|
||||
};
|
||||
loop {
|
||||
// Stop getting entries if we get exit signal
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
|
@ -447,7 +457,7 @@ impl ReplayStage {
|
|||
|
||||
let mut compute_bank_stats_time = Measure::start("compute_bank_stats");
|
||||
let newly_computed_slot_stats = Self::compute_bank_stats(
|
||||
&my_pubkey,
|
||||
&vote_account,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -480,6 +490,11 @@ impl ReplayStage {
|
|||
.select_forks(&frozen_banks, &tower, &progress, &ancestors, &bank_forks);
|
||||
select_forks_time.stop();
|
||||
|
||||
if let Some(heaviest_bank_on_same_voted_fork) = heaviest_bank_on_same_voted_fork.as_ref() {
|
||||
if let Some(my_latest_landed_vote) = progress.my_latest_landed_vote(heaviest_bank_on_same_voted_fork.slot()) {
|
||||
Self::refresh_last_vote(&mut tower, &cluster_info, heaviest_bank_on_same_voted_fork, &poh_recorder, my_latest_landed_vote, &vote_account, &authorized_voter_keypairs.read().unwrap(), &mut voted_signatures, has_new_vote_been_rooted, &mut last_vote_refresh_time);
|
||||
}
|
||||
}
|
||||
|
||||
let mut select_vote_and_reset_forks_time =
|
||||
Measure::start("select_vote_and_reset_forks");
|
||||
|
@ -1290,8 +1305,7 @@ impl ReplayStage {
|
|||
inc_new_counter_info!("replay_stage-voted_empty_bank", 1);
|
||||
}
|
||||
trace!("handle votable bank {}", bank.slot());
|
||||
let (new_root, tower_slots) = tower.record_bank_vote(bank, vote_account_pubkey);
|
||||
let last_vote = tower.last_vote_and_timestamp();
|
||||
let new_root = tower.record_bank_vote(bank, vote_account_pubkey);
|
||||
|
||||
if let Err(err) = tower.save(&cluster_info.keypair) {
|
||||
error!("Unable to save tower: {:?}", err);
|
||||
|
@ -1367,29 +1381,25 @@ impl ReplayStage {
|
|||
poh_recorder,
|
||||
vote_account_pubkey,
|
||||
authorized_voter_keypairs,
|
||||
last_vote,
|
||||
&tower_slots,
|
||||
tower,
|
||||
switch_fork_decision,
|
||||
vote_signatures,
|
||||
*has_new_vote_been_rooted,
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn push_vote(
|
||||
cluster_info: &ClusterInfo,
|
||||
bank: &Arc<Bank>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
fn generate_vote_tx(
|
||||
node_keypair: &Arc<Keypair>,
|
||||
bank: &Bank,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_keypairs: &[Arc<Keypair>],
|
||||
vote: Vote,
|
||||
tower: &[Slot],
|
||||
switch_fork_decision: &SwitchForkDecision,
|
||||
vote_signatures: &mut Vec<Signature>,
|
||||
has_new_vote_been_rooted: bool,
|
||||
) {
|
||||
) -> Option<Transaction> {
|
||||
if authorized_voter_keypairs.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let vote_account = match bank.get_vote_account(vote_account_pubkey) {
|
||||
None => {
|
||||
|
@ -1397,7 +1407,7 @@ impl ReplayStage {
|
|||
"Vote account {} does not exist. Unable to vote",
|
||||
vote_account_pubkey,
|
||||
);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
Some((_stake, vote_account)) => vote_account,
|
||||
};
|
||||
|
@ -1408,7 +1418,7 @@ impl ReplayStage {
|
|||
"Vote account {} is unreadable. Unable to vote",
|
||||
vote_account_pubkey,
|
||||
);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
Ok(vote_state) => vote_state,
|
||||
};
|
||||
|
@ -1421,7 +1431,7 @@ impl ReplayStage {
|
|||
vote_account_pubkey,
|
||||
bank.epoch()
|
||||
);
|
||||
return;
|
||||
return None;
|
||||
};
|
||||
|
||||
let authorized_voter_keypair = match authorized_voter_keypairs
|
||||
|
@ -1431,28 +1441,19 @@ impl ReplayStage {
|
|||
None => {
|
||||
warn!("The authorized keypair {} for vote account {} is not available. Unable to vote",
|
||||
authorized_voter_pubkey, vote_account_pubkey);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
Some(authorized_voter_keypair) => authorized_voter_keypair,
|
||||
};
|
||||
let node_keypair = &cluster_info.keypair;
|
||||
|
||||
// Send our last few votes along with the new one
|
||||
let vote_ix = if bank.slot() > Self::get_unlock_switch_vote_slot(bank.cluster_type()) {
|
||||
switch_fork_decision
|
||||
.to_vote_instruction(
|
||||
vote,
|
||||
&vote_account_pubkey,
|
||||
&authorized_voter_keypair.pubkey(),
|
||||
)
|
||||
.expect("Switch threshold failure should not lead to voting")
|
||||
} else {
|
||||
vote_instruction::vote(
|
||||
let vote_ix = switch_fork_decision
|
||||
.to_vote_instruction(
|
||||
vote,
|
||||
&vote_account_pubkey,
|
||||
&authorized_voter_keypair.pubkey(),
|
||||
vote,
|
||||
)
|
||||
};
|
||||
.expect("Switch threshold failure should not lead to voting");
|
||||
|
||||
let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
|
||||
|
||||
|
@ -1469,11 +1470,110 @@ impl ReplayStage {
|
|||
vote_signatures.clear();
|
||||
}
|
||||
|
||||
let _ = cluster_info.send_vote(
|
||||
&vote_tx,
|
||||
crate::banking_stage::next_leader_tpu(cluster_info, poh_recorder),
|
||||
Some(vote_tx)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn refresh_last_vote(
|
||||
tower: &mut Tower,
|
||||
cluster_info: &ClusterInfo,
|
||||
heaviest_bank_on_same_fork: &Bank,
|
||||
poh_recorder: &Mutex<PohRecorder>,
|
||||
my_latest_landed_vote: Slot,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_keypairs: &[Arc<Keypair>],
|
||||
vote_signatures: &mut Vec<Signature>,
|
||||
has_new_vote_been_rooted: bool,
|
||||
last_vote_refresh_time: &mut LastVoteRefreshTime,
|
||||
) {
|
||||
let last_voted_slot = tower.last_voted_slot();
|
||||
if last_voted_slot.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the vote if our latest vote hasn't landed, and the recent blockhash of the
|
||||
// last attempt at a vote transaction has expired
|
||||
let last_voted_slot = last_voted_slot.unwrap();
|
||||
if my_latest_landed_vote > last_voted_slot
|
||||
&& last_vote_refresh_time.last_print_time.elapsed().as_secs() >= 1
|
||||
{
|
||||
last_vote_refresh_time.last_print_time = Instant::now();
|
||||
info!("Last landed vote for slot {} in bank {} is greater than the current last vote for slot: {} tracked by Tower", my_latest_landed_vote, heaviest_bank_on_same_fork.slot(), last_voted_slot);
|
||||
}
|
||||
if my_latest_landed_vote >= last_voted_slot
|
||||
|| heaviest_bank_on_same_fork
|
||||
.check_hash_age(&tower.last_vote_tx_blockhash(), MAX_PROCESSING_AGE)
|
||||
.unwrap_or(false)
|
||||
// In order to avoid voting on multiple forks all past MAX_PROCESSING_AGE that don't
|
||||
// include the last voted blockhash
|
||||
|| last_vote_refresh_time.last_refresh_time.elapsed().as_millis() < MAX_VOTE_REFRESH_INTERVAL_MILLIS as u128
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: check the timestamp in this vote is correct, i.e. it shouldn't
|
||||
// have changed from the original timestamp of the vote.
|
||||
let vote_tx = Self::generate_vote_tx(
|
||||
&cluster_info.keypair,
|
||||
heaviest_bank_on_same_fork,
|
||||
vote_account_pubkey,
|
||||
authorized_voter_keypairs,
|
||||
tower.last_vote(),
|
||||
&SwitchForkDecision::SameFork,
|
||||
vote_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
);
|
||||
cluster_info.push_vote(tower, vote_tx);
|
||||
|
||||
if let Some(vote_tx) = vote_tx {
|
||||
let recent_blockhash = vote_tx.message.recent_blockhash;
|
||||
tower.refresh_last_vote_tx_blockhash(recent_blockhash);
|
||||
|
||||
// Send the votes to the TPU and gossip for network propagation
|
||||
let hash_string = format!("{}", recent_blockhash);
|
||||
datapoint_info!(
|
||||
"refresh_vote",
|
||||
("last_voted_slot", last_voted_slot, i64),
|
||||
("target_bank_slot", heaviest_bank_on_same_fork.slot(), i64),
|
||||
("target_bank_hash", hash_string, String),
|
||||
);
|
||||
let _ = cluster_info.send_vote(
|
||||
&vote_tx,
|
||||
crate::banking_stage::next_leader_tpu(cluster_info, poh_recorder),
|
||||
);
|
||||
cluster_info.refresh_vote(vote_tx, last_voted_slot);
|
||||
last_vote_refresh_time.last_refresh_time = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
fn push_vote(
|
||||
cluster_info: &ClusterInfo,
|
||||
bank: &Bank,
|
||||
poh_recorder: &Mutex<PohRecorder>,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_keypairs: &[Arc<Keypair>],
|
||||
tower: &mut Tower,
|
||||
switch_fork_decision: &SwitchForkDecision,
|
||||
vote_signatures: &mut Vec<Signature>,
|
||||
has_new_vote_been_rooted: bool,
|
||||
) {
|
||||
let vote_tx = Self::generate_vote_tx(
|
||||
&cluster_info.keypair,
|
||||
bank,
|
||||
vote_account_pubkey,
|
||||
authorized_voter_keypairs,
|
||||
tower.last_vote(),
|
||||
switch_fork_decision,
|
||||
vote_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
);
|
||||
if let Some(vote_tx) = vote_tx {
|
||||
tower.refresh_last_vote_tx_blockhash(vote_tx.message.recent_blockhash);
|
||||
let _ = cluster_info.send_vote(
|
||||
&vote_tx,
|
||||
crate::banking_stage::next_leader_tpu(cluster_info, poh_recorder),
|
||||
);
|
||||
cluster_info.push_vote(&tower.tower_slots(), vote_tx);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_commitment_cache(
|
||||
|
@ -1684,7 +1784,7 @@ impl ReplayStage {
|
|||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_bank_stats(
|
||||
my_pubkey: &Pubkey,
|
||||
my_vote_pubkey: &Pubkey,
|
||||
ancestors: &HashMap<u64, HashSet<u64>>,
|
||||
frozen_banks: &mut Vec<Arc<Bank>>,
|
||||
tower: &Tower,
|
||||
|
@ -1709,7 +1809,7 @@ impl ReplayStage {
|
|||
.computed;
|
||||
if !is_computed {
|
||||
let computed_bank_state = Tower::collect_vote_lockouts(
|
||||
my_pubkey,
|
||||
my_vote_pubkey,
|
||||
bank_slot,
|
||||
bank.vote_accounts().into_iter(),
|
||||
&ancestors,
|
||||
|
@ -1727,6 +1827,7 @@ impl ReplayStage {
|
|||
voted_stakes,
|
||||
total_stake,
|
||||
lockout_intervals,
|
||||
my_latest_landed_vote,
|
||||
..
|
||||
} = computed_bank_state;
|
||||
let stats = progress
|
||||
|
@ -1737,6 +1838,7 @@ impl ReplayStage {
|
|||
stats.lockout_intervals = lockout_intervals;
|
||||
stats.block_height = bank.block_height();
|
||||
stats.bank_hash = Some(bank.hash());
|
||||
stats.my_latest_landed_vote = my_latest_landed_vote;
|
||||
stats.computed = true;
|
||||
new_stats.push(bank_slot);
|
||||
datapoint_info!(
|
||||
|
@ -1747,7 +1849,7 @@ impl ReplayStage {
|
|||
);
|
||||
info!(
|
||||
"{} slot_weight: {} {} {} {}",
|
||||
my_pubkey,
|
||||
my_vote_pubkey,
|
||||
bank_slot,
|
||||
stats.weight,
|
||||
stats.fork_weight,
|
||||
|
@ -2396,6 +2498,7 @@ impl ReplayStage {
|
|||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cluster_info::Node,
|
||||
consensus::test::{initialize_state, VoteSimulator},
|
||||
consensus::Tower,
|
||||
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
||||
|
@ -2427,6 +2530,7 @@ pub(crate) mod tests {
|
|||
hash::{hash, Hash},
|
||||
instruction::InstructionError,
|
||||
packet::PACKET_DATA_SIZE,
|
||||
poh_config::PohConfig,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
|
@ -2461,10 +2565,15 @@ pub(crate) mod tests {
|
|||
|
||||
struct ReplayBlockstoreComponents {
|
||||
blockstore: Arc<Blockstore>,
|
||||
validator_voting_keys: HashMap<Pubkey, Pubkey>,
|
||||
validator_node_to_vote_keys: HashMap<Pubkey, Pubkey>,
|
||||
validator_authorized_voter_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
my_vote_pubkey: Pubkey,
|
||||
progress: ProgressMap,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
cluster_info: ClusterInfo,
|
||||
leader_schedule_cache: Arc<LeaderScheduleCache>,
|
||||
poh_recorder: Mutex<PohRecorder>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
tower: Tower,
|
||||
rpc_subscriptions: Arc<RpcSubscriptions>,
|
||||
}
|
||||
|
||||
|
@ -2477,10 +2586,11 @@ pub(crate) mod tests {
|
|||
let validator_authorized_voter_keypairs: Vec<_> =
|
||||
(0..20).map(|_| ValidatorVoteKeypairs::new_rand()).collect();
|
||||
|
||||
let validator_voting_keys: HashMap<_, _> = validator_authorized_voter_keypairs
|
||||
.iter()
|
||||
.map(|v| (v.node_keypair.pubkey(), v.vote_keypair.pubkey()))
|
||||
.collect();
|
||||
let validator_node_to_vote_keys: HashMap<Pubkey, Pubkey> =
|
||||
validator_authorized_voter_keypairs
|
||||
.iter()
|
||||
.map(|v| (v.node_keypair.pubkey(), v.vote_keypair.pubkey()))
|
||||
.collect();
|
||||
let GenesisConfigInfo { genesis_config, .. } =
|
||||
genesis_utils::create_genesis_config_with_vote_accounts(
|
||||
10_000,
|
||||
|
@ -2505,12 +2615,47 @@ pub(crate) mod tests {
|
|||
),
|
||||
);
|
||||
|
||||
// ClusterInfo
|
||||
let my_keypairs = &validator_authorized_voter_keypairs[0];
|
||||
let my_pubkey = my_keypairs.node_keypair.pubkey();
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost_with_pubkey(&my_pubkey).info,
|
||||
Arc::new(Keypair::from_bytes(&my_keypairs.node_keypair.to_bytes()).unwrap()),
|
||||
);
|
||||
assert_eq!(my_pubkey, cluster_info.id());
|
||||
|
||||
// Leader schedule cache
|
||||
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank0));
|
||||
|
||||
// PohRecorder
|
||||
let poh_recorder = Mutex::new(
|
||||
PohRecorder::new(
|
||||
bank0.tick_height(),
|
||||
bank0.last_blockhash(),
|
||||
bank0.slot(),
|
||||
None,
|
||||
bank0.ticks_per_slot(),
|
||||
&Pubkey::default(),
|
||||
&blockstore,
|
||||
&leader_schedule_cache,
|
||||
&Arc::new(PohConfig::default()),
|
||||
Arc::new(AtomicBool::new(false)),
|
||||
)
|
||||
.0,
|
||||
);
|
||||
|
||||
// BankForks
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0)));
|
||||
|
||||
// Tower
|
||||
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
|
||||
let tower = Tower::new_from_bankforks(
|
||||
&bank_forks.read().unwrap(),
|
||||
&ledger_path,
|
||||
&cluster_info.id(),
|
||||
&my_vote_pubkey,
|
||||
);
|
||||
|
||||
// RpcSubscriptions
|
||||
let optimistically_confirmed_bank =
|
||||
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
|
||||
|
@ -2522,12 +2667,23 @@ pub(crate) mod tests {
|
|||
optimistically_confirmed_bank,
|
||||
));
|
||||
|
||||
let validator_authorized_voter_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs> =
|
||||
validator_authorized_voter_keypairs
|
||||
.into_iter()
|
||||
.map(|keys| (keys.vote_keypair.pubkey(), keys))
|
||||
.collect();
|
||||
|
||||
ReplayBlockstoreComponents {
|
||||
blockstore,
|
||||
validator_voting_keys,
|
||||
validator_node_to_vote_keys,
|
||||
validator_authorized_voter_keypairs,
|
||||
my_vote_pubkey,
|
||||
progress,
|
||||
bank_forks,
|
||||
cluster_info,
|
||||
leader_schedule_cache,
|
||||
poh_recorder,
|
||||
bank_forks,
|
||||
tower,
|
||||
rpc_subscriptions,
|
||||
}
|
||||
}
|
||||
|
@ -2536,11 +2692,12 @@ pub(crate) mod tests {
|
|||
fn test_child_slots_of_same_parent() {
|
||||
let ReplayBlockstoreComponents {
|
||||
blockstore,
|
||||
validator_voting_keys,
|
||||
validator_node_to_vote_keys,
|
||||
mut progress,
|
||||
bank_forks,
|
||||
leader_schedule_cache,
|
||||
rpc_subscriptions,
|
||||
..
|
||||
} = replay_blockstore_components();
|
||||
|
||||
// Insert a non-root bank so that the propagation logic will update this
|
||||
|
@ -2555,7 +2712,9 @@ pub(crate) mod tests {
|
|||
ForkProgress::new_from_bank(
|
||||
&bank1,
|
||||
bank1.collector_id(),
|
||||
validator_voting_keys.get(&bank1.collector_id()).unwrap(),
|
||||
validator_node_to_vote_keys
|
||||
.get(&bank1.collector_id())
|
||||
.unwrap(),
|
||||
Some(0),
|
||||
DuplicateStats::default(),
|
||||
0,
|
||||
|
@ -2625,7 +2784,7 @@ pub(crate) mod tests {
|
|||
];
|
||||
for slot in expected_leader_slots {
|
||||
let leader = leader_schedule_cache.slot_leader_at(slot, None).unwrap();
|
||||
let vote_key = validator_voting_keys.get(&leader).unwrap();
|
||||
let vote_key = validator_node_to_vote_keys.get(&leader).unwrap();
|
||||
assert!(progress
|
||||
.get_propagated_stats(1)
|
||||
.unwrap()
|
||||
|
@ -3287,15 +3446,16 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
fn test_compute_bank_stats_confirmed() {
|
||||
let vote_keypairs = ValidatorVoteKeypairs::new_rand();
|
||||
let node_pubkey = vote_keypairs.node_keypair.pubkey();
|
||||
let keypairs: HashMap<_, _> = vec![(node_pubkey, vote_keypairs)].into_iter().collect();
|
||||
let my_node_pubkey = vote_keypairs.node_keypair.pubkey();
|
||||
let my_vote_pubkey = vote_keypairs.vote_keypair.pubkey();
|
||||
let keypairs: HashMap<_, _> = vec![(my_node_pubkey, vote_keypairs)].into_iter().collect();
|
||||
|
||||
let (bank_forks, mut progress, mut heaviest_subtree_fork_choice) =
|
||||
initialize_state(&keypairs, 10_000);
|
||||
let mut latest_validator_votes_for_frozen_banks =
|
||||
LatestValidatorVotesForFrozenBanks::default();
|
||||
let bank0 = bank_forks.get(0).unwrap().clone();
|
||||
let my_keypairs = keypairs.get(&node_pubkey).unwrap();
|
||||
let my_keypairs = keypairs.get(&my_node_pubkey).unwrap();
|
||||
let vote_tx = vote_transaction::new_vote_transaction(
|
||||
vec![0],
|
||||
bank0.hash(),
|
||||
|
@ -3307,7 +3467,7 @@ pub(crate) mod tests {
|
|||
);
|
||||
|
||||
let bank_forks = RwLock::new(bank_forks);
|
||||
let bank1 = Bank::new_from_parent(&bank0, &node_pubkey, 1);
|
||||
let bank1 = Bank::new_from_parent(&bank0, &my_node_pubkey, 1);
|
||||
bank1.process_transaction(&vote_tx).unwrap();
|
||||
bank1.freeze();
|
||||
|
||||
|
@ -3322,7 +3482,7 @@ pub(crate) mod tests {
|
|||
.collect();
|
||||
let tower = Tower::new_for_tests(0, 0.67);
|
||||
let newly_computed = ReplayStage::compute_bank_stats(
|
||||
&node_pubkey,
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -3373,7 +3533,7 @@ pub(crate) mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
let newly_computed = ReplayStage::compute_bank_stats(
|
||||
&node_pubkey,
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -3409,7 +3569,7 @@ pub(crate) mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
let newly_computed = ReplayStage::compute_bank_stats(
|
||||
&node_pubkey,
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -3428,8 +3588,8 @@ pub(crate) mod tests {
|
|||
fn test_same_weight_select_lower_slot() {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(1);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let tower = Tower::new_with_key(&node_pubkey);
|
||||
let my_node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let tower = Tower::new_with_key(&my_node_pubkey);
|
||||
|
||||
// Create the tree of banks in a BankForks object
|
||||
let forks = tr(0) / (tr(1)) / (tr(2));
|
||||
|
@ -3446,8 +3606,10 @@ pub(crate) mod tests {
|
|||
let mut latest_validator_votes_for_frozen_banks =
|
||||
LatestValidatorVotesForFrozenBanks::default();
|
||||
let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
|
||||
|
||||
let my_vote_pubkey = vote_simulator.vote_pubkeys[0];
|
||||
ReplayStage::compute_bank_stats(
|
||||
&node_pubkey,
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -3498,8 +3660,8 @@ pub(crate) mod tests {
|
|||
fn test_child_bank_heavier() {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(1);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
let my_node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&my_node_pubkey);
|
||||
|
||||
// Create the tree of banks in a BankForks object
|
||||
let forks = tr(0) / (tr(1) / (tr(2) / (tr(3))));
|
||||
|
@ -3507,13 +3669,13 @@ pub(crate) mod tests {
|
|||
// Set the voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let votes = vec![0, 2];
|
||||
cluster_votes.insert(node_pubkey, votes.clone());
|
||||
cluster_votes.insert(my_node_pubkey, votes.clone());
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes);
|
||||
|
||||
// Fill banks with votes
|
||||
for vote in votes {
|
||||
assert!(vote_simulator
|
||||
.simulate_vote(vote, &node_pubkey, &mut tower,)
|
||||
.simulate_vote(vote, &my_node_pubkey, &mut tower,)
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
|
@ -3526,8 +3688,9 @@ pub(crate) mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let my_vote_pubkey = vote_simulator.vote_pubkeys[0];
|
||||
ReplayStage::compute_bank_stats(
|
||||
&node_pubkey,
|
||||
&my_vote_pubkey,
|
||||
&vote_simulator.bank_forks.read().unwrap().ancestors(),
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -4333,7 +4496,7 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
fn test_leader_snapshot_restart_propagation() {
|
||||
let ReplayBlockstoreComponents {
|
||||
validator_voting_keys,
|
||||
validator_node_to_vote_keys,
|
||||
mut progress,
|
||||
bank_forks,
|
||||
leader_schedule_cache,
|
||||
|
@ -4368,7 +4531,7 @@ pub(crate) mod tests {
|
|||
let vote_tracker = VoteTracker::default();
|
||||
|
||||
// Add votes
|
||||
for vote_key in validator_voting_keys.values() {
|
||||
for vote_key in validator_node_to_vote_keys.values() {
|
||||
vote_tracker.insert_vote(root_bank.slot(), *vote_key);
|
||||
}
|
||||
|
||||
|
@ -4377,7 +4540,7 @@ pub(crate) mod tests {
|
|||
// Update propagation status
|
||||
let tower = Tower::new_for_tests(0, 0.67);
|
||||
ReplayStage::compute_bank_stats(
|
||||
&my_pubkey,
|
||||
&validator_node_to_vote_keys[&my_pubkey],
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
|
@ -4647,6 +4810,234 @@ pub(crate) mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replay_stage_refresh_last_vote() {
|
||||
let ReplayBlockstoreComponents {
|
||||
mut validator_authorized_voter_keypairs,
|
||||
cluster_info,
|
||||
poh_recorder,
|
||||
bank_forks,
|
||||
mut tower,
|
||||
my_vote_pubkey,
|
||||
..
|
||||
} = replay_blockstore_components();
|
||||
|
||||
let mut last_vote_refresh_time = LastVoteRefreshTime {
|
||||
last_refresh_time: Instant::now(),
|
||||
last_print_time: Instant::now(),
|
||||
};
|
||||
let has_new_vote_been_rooted = false;
|
||||
let mut voted_signatures = vec![];
|
||||
|
||||
let my_vote_keypair = vec![Arc::new(
|
||||
validator_authorized_voter_keypairs
|
||||
.remove(&my_vote_pubkey)
|
||||
.unwrap()
|
||||
.vote_keypair,
|
||||
)];
|
||||
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||
|
||||
fn fill_bank_with_ticks(bank: &Bank) {
|
||||
let parent_distance = bank.slot() - bank.parent_slot();
|
||||
for _ in 0..parent_distance {
|
||||
let last_blockhash = bank.last_blockhash();
|
||||
while bank.last_blockhash() == last_blockhash {
|
||||
bank.register_tick(&Hash::new_unique())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate landing a vote for slot 0 landing in slot 1
|
||||
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
|
||||
fill_bank_with_ticks(&bank1);
|
||||
tower.record_bank_vote(&bank0, &my_vote_pubkey);
|
||||
ReplayStage::push_vote(
|
||||
&cluster_info,
|
||||
&bank0,
|
||||
&poh_recorder,
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut tower,
|
||||
&SwitchForkDecision::SameFork,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
);
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(0);
|
||||
assert_eq!(votes.len(), 1);
|
||||
let vote_tx = &votes[0];
|
||||
assert_eq!(vote_tx.message.recent_blockhash, bank0.last_blockhash());
|
||||
assert_eq!(tower.last_vote_tx_blockhash(), bank0.last_blockhash());
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 0);
|
||||
bank1.process_transaction(vote_tx).unwrap();
|
||||
bank1.freeze();
|
||||
|
||||
// Trying to refresh the vote for bank 0 in bank 1 or bank 2 won't succeed because
|
||||
// the last vote has landed already
|
||||
let bank2 = Arc::new(Bank::new_from_parent(&bank1, &Pubkey::default(), 2));
|
||||
fill_bank_with_ticks(&bank2);
|
||||
bank2.freeze();
|
||||
for refresh_bank in &[&bank1, &bank2] {
|
||||
ReplayStage::refresh_last_vote(
|
||||
&mut tower,
|
||||
&cluster_info,
|
||||
refresh_bank,
|
||||
&poh_recorder,
|
||||
Tower::last_voted_slot_in_bank(&refresh_bank, &my_vote_pubkey).unwrap(),
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
);
|
||||
|
||||
// No new votes have been submitted to gossip
|
||||
let (_, votes, _max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert!(votes.is_empty());
|
||||
// Tower's latest vote tx blockhash hasn't changed either
|
||||
assert_eq!(tower.last_vote_tx_blockhash(), bank0.last_blockhash());
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 0);
|
||||
}
|
||||
|
||||
// Simulate submitting a new vote for bank 1 to the network, but the vote
|
||||
// not landing
|
||||
tower.record_bank_vote(&bank1, &my_vote_pubkey);
|
||||
ReplayStage::push_vote(
|
||||
&cluster_info,
|
||||
&bank1,
|
||||
&poh_recorder,
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut tower,
|
||||
&SwitchForkDecision::SameFork,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
);
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert_eq!(votes.len(), 1);
|
||||
let vote_tx = &votes[0];
|
||||
assert_eq!(vote_tx.message.recent_blockhash, bank1.last_blockhash());
|
||||
assert_eq!(tower.last_vote_tx_blockhash(), bank1.last_blockhash());
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 1);
|
||||
|
||||
// Trying to refresh the vote for bank 1 in bank 2 won't succeed because
|
||||
// the last vote has not expired yet
|
||||
ReplayStage::refresh_last_vote(
|
||||
&mut tower,
|
||||
&cluster_info,
|
||||
&bank2,
|
||||
&poh_recorder,
|
||||
Tower::last_voted_slot_in_bank(&bank2, &my_vote_pubkey).unwrap(),
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
);
|
||||
// No new votes have been submitted to gossip
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert!(votes.is_empty());
|
||||
assert_eq!(tower.last_vote_tx_blockhash(), bank1.last_blockhash());
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 1);
|
||||
|
||||
// Create a bank where the last vote transaction will have expired
|
||||
let expired_bank = Arc::new(Bank::new_from_parent(
|
||||
&bank2,
|
||||
&Pubkey::default(),
|
||||
bank2.slot() + MAX_PROCESSING_AGE as Slot,
|
||||
));
|
||||
fill_bank_with_ticks(&expired_bank);
|
||||
expired_bank.freeze();
|
||||
|
||||
// Now trying to refresh the vote for slot 1 will succeed because the recent blockhash
|
||||
// of the last vote transaction has expired
|
||||
last_vote_refresh_time.last_refresh_time = last_vote_refresh_time
|
||||
.last_refresh_time
|
||||
.checked_sub(Duration::from_millis(
|
||||
MAX_VOTE_REFRESH_INTERVAL_MILLIS as u64 + 1,
|
||||
))
|
||||
.unwrap();
|
||||
let clone_refresh_time = last_vote_refresh_time.last_refresh_time;
|
||||
ReplayStage::refresh_last_vote(
|
||||
&mut tower,
|
||||
&cluster_info,
|
||||
&expired_bank,
|
||||
&poh_recorder,
|
||||
Tower::last_voted_slot_in_bank(&expired_bank, &my_vote_pubkey).unwrap(),
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
);
|
||||
assert!(last_vote_refresh_time.last_refresh_time > clone_refresh_time);
|
||||
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert_eq!(votes.len(), 1);
|
||||
let vote_tx = &votes[0];
|
||||
assert_eq!(
|
||||
vote_tx.message.recent_blockhash,
|
||||
expired_bank.last_blockhash()
|
||||
);
|
||||
assert_eq!(
|
||||
tower.last_vote_tx_blockhash(),
|
||||
expired_bank.last_blockhash()
|
||||
);
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 1);
|
||||
|
||||
// Processing the vote transaction should be valid
|
||||
let expired_bank_child = Arc::new(Bank::new_from_parent(
|
||||
&expired_bank,
|
||||
&Pubkey::default(),
|
||||
expired_bank.slot() + 1,
|
||||
));
|
||||
expired_bank_child.process_transaction(vote_tx).unwrap();
|
||||
let (_stake, vote_account) = expired_bank_child
|
||||
.get_vote_account(&my_vote_pubkey)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vote_account.vote_state().as_ref().unwrap().tower(),
|
||||
vec![0, 1]
|
||||
);
|
||||
fill_bank_with_ticks(&expired_bank_child);
|
||||
expired_bank_child.freeze();
|
||||
|
||||
// Trying to refresh the vote on a sibling bank where:
|
||||
// 1) The vote for slot 1 hasn't landed
|
||||
// 2) The latest refresh vote transaction's recent blockhash (the sibling's hash) doesn't exist
|
||||
// This will still not refresh because `MAX_VOTE_REFRESH_INTERVAL_MILLIS` has not expired yet
|
||||
let expired_bank_sibling = Arc::new(Bank::new_from_parent(
|
||||
&bank2,
|
||||
&Pubkey::default(),
|
||||
expired_bank_child.slot() + 1,
|
||||
));
|
||||
fill_bank_with_ticks(&expired_bank_sibling);
|
||||
expired_bank_sibling.freeze();
|
||||
// Set the last refresh to now, shouldn't refresh because the last refresh just happened.
|
||||
last_vote_refresh_time.last_refresh_time = Instant::now();
|
||||
ReplayStage::refresh_last_vote(
|
||||
&mut tower,
|
||||
&cluster_info,
|
||||
&expired_bank_sibling,
|
||||
&poh_recorder,
|
||||
Tower::last_voted_slot_in_bank(&expired_bank_sibling, &my_vote_pubkey).unwrap(),
|
||||
&my_vote_pubkey,
|
||||
&my_vote_keypair,
|
||||
&mut voted_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
);
|
||||
let (_, votes, _max_ts) = cluster_info.get_votes(max_ts);
|
||||
assert!(votes.is_empty());
|
||||
assert_eq!(
|
||||
vote_tx.message.recent_blockhash,
|
||||
expired_bank.last_blockhash()
|
||||
);
|
||||
assert_eq!(
|
||||
tower.last_vote_tx_blockhash(),
|
||||
expired_bank.last_blockhash()
|
||||
);
|
||||
assert_eq!(tower.last_voted_slot().unwrap(), 1);
|
||||
}
|
||||
|
||||
fn run_compute_and_select_forks(
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
progress: &mut ProgressMap,
|
||||
|
|
|
@ -52,7 +52,7 @@ use solana_sdk::{
|
|||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::{BTreeSet, HashMap, HashSet},
|
||||
fs,
|
||||
io::Read,
|
||||
iter,
|
||||
|
@ -689,7 +689,6 @@ fn test_kill_partition_switch_threshold_progress() {
|
|||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
// Steps in this test:
|
||||
// We want to create a situation like:
|
||||
/*
|
||||
|
@ -714,7 +713,7 @@ fn test_kill_partition_switch_threshold_progress() {
|
|||
// 6) Resolve the partition so that the 2% repairs the other fork, and tries to switch,
|
||||
// stalling the network.
|
||||
|
||||
fn test_fork_choice_ingest_votes_from_gossip() {
|
||||
fn test_fork_choice_refresh_old_votes() {
|
||||
solana_logger::setup_with_default(RUST_LOG_FILTER);
|
||||
let max_switch_threshold_failure_pct = 1.0 - 2.0 * SWITCH_FORK_THRESHOLD;
|
||||
let total_stake = 100;
|
||||
|
@ -790,36 +789,41 @@ fn test_fork_choice_ingest_votes_from_gossip() {
|
|||
|
||||
info!("Opened blockstores");
|
||||
|
||||
// Get latest votes
|
||||
let lighter_fork_latest_vote = last_vote_in_tower(
|
||||
&lighter_fork_ledger_path,
|
||||
&context.lighter_fork_validator_key,
|
||||
)
|
||||
.unwrap();
|
||||
let heaviest_fork_latest_vote =
|
||||
last_vote_in_tower(&heaviest_ledger_path, &context.heaviest_validator_key).unwrap();
|
||||
|
||||
// Find the first slot on the smaller fork
|
||||
let mut first_slot_in_lighter_partition = 0;
|
||||
for ((heavier_slot, heavier_slot_meta), (lighter_slot, _lighter_slot_meta)) in
|
||||
heaviest_blockstore
|
||||
.slot_meta_iterator(0)
|
||||
.unwrap()
|
||||
.zip(lighter_fork_blockstore.slot_meta_iterator(0).unwrap())
|
||||
{
|
||||
if heavier_slot != lighter_slot {
|
||||
// Find the parent of the fork point
|
||||
let last_common_ancestor = heavier_slot_meta.parent_slot;
|
||||
let lighter_fork_parent_meta = lighter_fork_blockstore
|
||||
.meta(last_common_ancestor)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
// Lighter fork should only see one next slots, since only two validators
|
||||
// could have generated childrenof `parent`, and the lighter fork *definitely*
|
||||
// doesn't see the other fork's child, otherwise `heavier_slot != lighter_slot`
|
||||
// would not have triggere above.
|
||||
assert_eq!(lighter_fork_parent_meta.next_slots.len(), 1);
|
||||
let lighter_fork_child = lighter_fork_parent_meta.next_slots[0];
|
||||
assert_ne!(first_slot_in_lighter_partition, heavier_slot);
|
||||
first_slot_in_lighter_partition = lighter_fork_child;
|
||||
info!(
|
||||
"First slot in lighter partition is {}",
|
||||
first_slot_in_lighter_partition
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let lighter_ancestors: BTreeSet<Slot> = std::iter::once(lighter_fork_latest_vote)
|
||||
.chain(AncestorIterator::new(
|
||||
lighter_fork_latest_vote,
|
||||
&lighter_fork_blockstore,
|
||||
))
|
||||
.collect();
|
||||
let heavier_ancestors: BTreeSet<Slot> = std::iter::once(heaviest_fork_latest_vote)
|
||||
.chain(AncestorIterator::new(
|
||||
heaviest_fork_latest_vote,
|
||||
&heaviest_blockstore,
|
||||
))
|
||||
.collect();
|
||||
let first_slot_in_lighter_partition = *lighter_ancestors
|
||||
.iter()
|
||||
.zip(heavier_ancestors.iter())
|
||||
.find(|(x, y)| x != y)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
// Must have been updated in the above loop
|
||||
assert!(first_slot_in_lighter_partition != 0);
|
||||
info!(
|
||||
"First slot in lighter partition is {}",
|
||||
first_slot_in_lighter_partition
|
||||
);
|
||||
|
||||
assert!(first_slot_in_lighter_partition != 0);
|
||||
|
||||
|
|
Loading…
Reference in New Issue