Clean up opt conf verifier and vote state tracker (#13081)
* Clean up opt conf verifier and vote state tracker * Update test to follow new message and some knob * Rename
This commit is contained in:
parent
b5170b993e
commit
0264147d42
|
@ -27,7 +27,7 @@ use solana_runtime::{
|
||||||
vote_sender_types::{ReplayVoteReceiver, ReplayedVote},
|
vote_sender_types::{ReplayVoteReceiver, ReplayedVote},
|
||||||
};
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{Epoch, Slot},
|
clock::{Epoch, Slot, DEFAULT_MS_PER_SLOT},
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
@ -98,7 +98,7 @@ impl VoteTracker {
|
||||||
epoch_schedule: *root_bank.epoch_schedule(),
|
epoch_schedule: *root_bank.epoch_schedule(),
|
||||||
..VoteTracker::default()
|
..VoteTracker::default()
|
||||||
};
|
};
|
||||||
vote_tracker.process_new_root_bank(&root_bank);
|
vote_tracker.progress_with_new_root_bank(&root_bank);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
||||||
root_bank.get_leader_schedule_epoch(root_bank.slot())
|
root_bank.get_leader_schedule_epoch(root_bank.slot())
|
||||||
|
@ -174,7 +174,7 @@ impl VoteTracker {
|
||||||
self.keys.get_or_insert(&pubkey);
|
self.keys.get_or_insert(&pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_leader_schedule_epoch(&self, root_bank: &Bank) {
|
fn progress_leader_schedule_epoch(&self, root_bank: &Bank) {
|
||||||
// Update with any newly calculated epoch state about future epochs
|
// Update with any newly calculated epoch state about future epochs
|
||||||
let start_leader_schedule_epoch = *self.leader_schedule_epoch.read().unwrap();
|
let start_leader_schedule_epoch = *self.leader_schedule_epoch.read().unwrap();
|
||||||
let mut greatest_leader_schedule_epoch = start_leader_schedule_epoch;
|
let mut greatest_leader_schedule_epoch = start_leader_schedule_epoch;
|
||||||
|
@ -205,7 +205,7 @@ impl VoteTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_new_root(&self, root_bank: &Bank) {
|
fn purge_stale_state(&self, root_bank: &Bank) {
|
||||||
// Purge any outdated slot data
|
// Purge any outdated slot data
|
||||||
let new_root = root_bank.slot();
|
let new_root = root_bank.slot();
|
||||||
let root_epoch = root_bank.epoch();
|
let root_epoch = root_bank.epoch();
|
||||||
|
@ -220,15 +220,15 @@ impl VoteTracker {
|
||||||
self.epoch_authorized_voters
|
self.epoch_authorized_voters
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.retain(|epoch, _| epoch >= &root_epoch);
|
.retain(|epoch, _| *epoch >= root_epoch);
|
||||||
self.keys.purge();
|
self.keys.purge();
|
||||||
*self.current_epoch.write().unwrap() = root_epoch;
|
*self.current_epoch.write().unwrap() = root_epoch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_new_root_bank(&self, root_bank: &Bank) {
|
fn progress_with_new_root_bank(&self, root_bank: &Bank) {
|
||||||
self.update_leader_schedule_epoch(root_bank);
|
self.progress_leader_schedule_epoch(root_bank);
|
||||||
self.update_new_root(root_bank);
|
self.purge_stale_state(root_bank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +425,7 @@ impl ClusterInfoVoteListener {
|
||||||
blockstore: Arc<Blockstore>,
|
blockstore: Arc<Blockstore>,
|
||||||
bank_notification_sender: Option<BankNotificationSender>,
|
bank_notification_sender: Option<BankNotificationSender>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut optimistic_confirmation_verifier =
|
let mut confirmation_verifier =
|
||||||
OptimisticConfirmationVerifier::new(bank_forks.read().unwrap().root());
|
OptimisticConfirmationVerifier::new(bank_forks.read().unwrap().root());
|
||||||
let mut last_process_root = Instant::now();
|
let mut last_process_root = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
|
@ -434,21 +434,21 @@ impl ClusterInfoVoteListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_bank = bank_forks.read().unwrap().root_bank().clone();
|
let root_bank = bank_forks.read().unwrap().root_bank().clone();
|
||||||
if last_process_root.elapsed().as_millis() > 400 {
|
if last_process_root.elapsed().as_millis() > DEFAULT_MS_PER_SLOT as u128 {
|
||||||
let unrooted_optimistic_slots = optimistic_confirmation_verifier
|
let unrooted_optimistic_slots = confirmation_verifier
|
||||||
.get_unrooted_optimistic_slots(&root_bank, &blockstore);
|
.verify_for_unrooted_optimistic_slots(&root_bank, &blockstore);
|
||||||
// SlotVoteTracker's for all `slots` in `unrooted_optimistic_slots`
|
// SlotVoteTracker's for all `slots` in `unrooted_optimistic_slots`
|
||||||
// should still be available because we haven't purged in
|
// should still be available because we haven't purged in
|
||||||
// `process_new_root_bank()` yet, which is called below
|
// `progress_with_new_root_bank()` yet, which is called below
|
||||||
OptimisticConfirmationVerifier::log_unrooted_optimistic_slots(
|
OptimisticConfirmationVerifier::log_unrooted_optimistic_slots(
|
||||||
&root_bank,
|
&root_bank,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&unrooted_optimistic_slots,
|
&unrooted_optimistic_slots,
|
||||||
);
|
);
|
||||||
vote_tracker.process_new_root_bank(&root_bank);
|
vote_tracker.progress_with_new_root_bank(&root_bank);
|
||||||
last_process_root = Instant::now();
|
last_process_root = Instant::now();
|
||||||
}
|
}
|
||||||
let optimistic_confirmed_slots = Self::get_and_process_votes(
|
let confirmed_slots = Self::listen_and_confirm_votes(
|
||||||
&gossip_vote_txs_receiver,
|
&gossip_vote_txs_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&root_bank,
|
&root_bank,
|
||||||
|
@ -457,19 +457,17 @@ impl ClusterInfoVoteListener {
|
||||||
&replay_votes_receiver,
|
&replay_votes_receiver,
|
||||||
&bank_notification_sender,
|
&bank_notification_sender,
|
||||||
);
|
);
|
||||||
|
match confirmed_slots {
|
||||||
if let Err(e) = optimistic_confirmed_slots {
|
Ok(confirmed_slots) => {
|
||||||
match e {
|
confirmation_verifier.add_new_optimistic_confirmed_slots(confirmed_slots);
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Timeout)
|
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Timeout)
|
||||||
| Error::ReadyTimeoutError => (),
|
| Error::ReadyTimeoutError => (),
|
||||||
_ => {
|
_ => {
|
||||||
error!("thread {:?} error {:?}", thread::current().name(), e);
|
error!("thread {:?} error {:?}", thread::current().name(), e);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} else {
|
|
||||||
let optimistic_confirmed_slots = optimistic_confirmed_slots.unwrap();
|
|
||||||
optimistic_confirmation_verifier
|
|
||||||
.add_new_optimistic_confirmed_slots(optimistic_confirmed_slots);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,7 +481,7 @@ impl ClusterInfoVoteListener {
|
||||||
verified_vote_sender: &VerifiedVoteSender,
|
verified_vote_sender: &VerifiedVoteSender,
|
||||||
replay_votes_receiver: &ReplayVoteReceiver,
|
replay_votes_receiver: &ReplayVoteReceiver,
|
||||||
) -> Result<Vec<(Slot, Hash)>> {
|
) -> Result<Vec<(Slot, Hash)>> {
|
||||||
Self::get_and_process_votes(
|
Self::listen_and_confirm_votes(
|
||||||
gossip_vote_txs_receiver,
|
gossip_vote_txs_receiver,
|
||||||
vote_tracker,
|
vote_tracker,
|
||||||
root_bank,
|
root_bank,
|
||||||
|
@ -494,7 +492,7 @@ impl ClusterInfoVoteListener {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_and_process_votes(
|
fn listen_and_confirm_votes(
|
||||||
gossip_vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
|
gossip_vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
|
||||||
vote_tracker: &VoteTracker,
|
vote_tracker: &VoteTracker,
|
||||||
root_bank: &Bank,
|
root_bank: &Bank,
|
||||||
|
@ -523,7 +521,7 @@ impl ClusterInfoVoteListener {
|
||||||
let gossip_vote_txs: Vec<_> = gossip_vote_txs_receiver.try_iter().flatten().collect();
|
let gossip_vote_txs: Vec<_> = gossip_vote_txs_receiver.try_iter().flatten().collect();
|
||||||
let replay_votes: Vec<_> = replay_votes_receiver.try_iter().collect();
|
let replay_votes: Vec<_> = replay_votes_receiver.try_iter().collect();
|
||||||
if !gossip_vote_txs.is_empty() || !replay_votes.is_empty() {
|
if !gossip_vote_txs.is_empty() || !replay_votes.is_empty() {
|
||||||
return Ok(Self::process_votes(
|
return Ok(Self::filter_and_confirm_with_new_votes(
|
||||||
vote_tracker,
|
vote_tracker,
|
||||||
gossip_vote_txs,
|
gossip_vote_txs,
|
||||||
replay_votes,
|
replay_votes,
|
||||||
|
@ -541,7 +539,7 @@ impl ClusterInfoVoteListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn update_new_votes(
|
fn track_new_votes_and_notify_confirmations(
|
||||||
vote: Vote,
|
vote: Vote,
|
||||||
vote_pubkey: &Pubkey,
|
vote_pubkey: &Pubkey,
|
||||||
vote_tracker: &VoteTracker,
|
vote_tracker: &VoteTracker,
|
||||||
|
@ -557,56 +555,52 @@ impl ClusterInfoVoteListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_vote_slot = vote.slots.last().unwrap();
|
let last_vote_slot = *vote.slots.last().unwrap();
|
||||||
|
let last_vote_hash = vote.hash;
|
||||||
|
|
||||||
let root = root_bank.slot();
|
let root = root_bank.slot();
|
||||||
let last_vote_hash = vote.hash;
|
|
||||||
let mut is_new_vote = false;
|
let mut is_new_vote = false;
|
||||||
for slot in vote.slots.iter().rev() {
|
// If slot is before the root, ignore it
|
||||||
// If slot is before the root, or so far ahead we don't have
|
for slot in vote.slots.iter().filter(|slot| **slot > root).rev() {
|
||||||
// stake information, then ignore it
|
let slot = *slot;
|
||||||
let epoch = root_bank.epoch_schedule().get_epoch(*slot);
|
|
||||||
|
// if we don't have stake information, ignore it
|
||||||
|
let epoch = root_bank.epoch_schedule().get_epoch(slot);
|
||||||
let epoch_stakes = root_bank.epoch_stakes(epoch);
|
let epoch_stakes = root_bank.epoch_stakes(epoch);
|
||||||
if *slot <= root || epoch_stakes.is_none() {
|
if epoch_stakes.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let epoch_stakes = epoch_stakes.unwrap();
|
let epoch_stakes = epoch_stakes.unwrap();
|
||||||
let epoch_vote_accounts = Stakes::vote_accounts(epoch_stakes.stakes());
|
|
||||||
let total_epoch_stake = epoch_stakes.total_stake();
|
|
||||||
let unduplicated_pubkey = vote_tracker.keys.get_or_insert(&vote_pubkey);
|
let unduplicated_pubkey = vote_tracker.keys.get_or_insert(&vote_pubkey);
|
||||||
|
|
||||||
// The last vote slot, which is the greatest slot in the stack
|
// The last vote slot, which is the greatest slot in the stack
|
||||||
// of votes in a vote transaction, qualifies for optimistic confirmation.
|
// of votes in a vote transaction, qualifies for optimistic confirmation.
|
||||||
let update_optimistic_confirmation_info = if slot == last_vote_slot {
|
if slot == last_vote_slot {
|
||||||
let stake = epoch_vote_accounts
|
let vote_accounts = Stakes::vote_accounts(epoch_stakes.stakes());
|
||||||
|
let stake = vote_accounts
|
||||||
.get(&vote_pubkey)
|
.get(&vote_pubkey)
|
||||||
.map(|(stake, _)| *stake)
|
.map(|(stake, _)| *stake)
|
||||||
.unwrap_or(0);
|
.unwrap_or_default();
|
||||||
Some((stake, last_vote_hash))
|
let total_stake = epoch_stakes.total_stake();
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// If this vote for this slot qualifies for optimistic confirmation
|
|
||||||
if let Some((stake, hash)) = update_optimistic_confirmation_info {
|
|
||||||
// Fast track processing of the last slot in a vote transactions
|
// Fast track processing of the last slot in a vote transactions
|
||||||
// so that notifications for optimistic confirmation can be sent
|
// so that notifications for optimistic confirmation can be sent
|
||||||
// as soon as possible.
|
// as soon as possible.
|
||||||
let (is_confirmed, is_new) = Self::add_optimistic_confirmation_vote(
|
let (is_confirmed, is_new) = Self::track_optimistic_confirmation_vote(
|
||||||
vote_tracker,
|
vote_tracker,
|
||||||
*slot,
|
last_vote_slot,
|
||||||
hash,
|
last_vote_hash,
|
||||||
unduplicated_pubkey.clone(),
|
unduplicated_pubkey.clone(),
|
||||||
stake,
|
stake,
|
||||||
total_epoch_stake,
|
total_stake,
|
||||||
);
|
);
|
||||||
|
|
||||||
if is_confirmed {
|
if is_confirmed {
|
||||||
new_optimistic_confirmed_slots.push((*slot, last_vote_hash));
|
new_optimistic_confirmed_slots.push((last_vote_slot, last_vote_hash));
|
||||||
// Notify subscribers about new optimistic confirmation
|
// Notify subscribers about new optimistic confirmation
|
||||||
if let Some(sender) = bank_notification_sender {
|
if let Some(sender) = bank_notification_sender {
|
||||||
sender
|
sender
|
||||||
.send(BankNotification::OptimisticallyConfirmed(*slot))
|
.send(BankNotification::OptimisticallyConfirmed(last_vote_slot))
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
warn!("bank_notification_sender failed: {:?}", err)
|
warn!("bank_notification_sender failed: {:?}", err)
|
||||||
});
|
});
|
||||||
|
@ -617,7 +611,7 @@ impl ClusterInfoVoteListener {
|
||||||
// By now:
|
// By now:
|
||||||
// 1) The vote must have come from ReplayStage,
|
// 1) The vote must have come from ReplayStage,
|
||||||
// 2) We've seen this vote from replay for this hash before
|
// 2) We've seen this vote from replay for this hash before
|
||||||
// (`add_optimistic_confirmation_vote()` will not set `is_new == true`
|
// (`track_optimistic_confirmation_vote()` will not set `is_new == true`
|
||||||
// for same slot different hash), so short circuit because this vote
|
// for same slot different hash), so short circuit because this vote
|
||||||
// has no new information
|
// has no new information
|
||||||
|
|
||||||
|
@ -629,7 +623,7 @@ impl ClusterInfoVoteListener {
|
||||||
is_new_vote = is_new;
|
is_new_vote = is_new;
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.entry(*slot)
|
diff.entry(slot)
|
||||||
.or_default()
|
.or_default()
|
||||||
.entry(unduplicated_pubkey)
|
.entry(unduplicated_pubkey)
|
||||||
.and_modify(|seen_in_gossip_previously| {
|
.and_modify(|seen_in_gossip_previously| {
|
||||||
|
@ -644,7 +638,40 @@ impl ClusterInfoVoteListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_votes(
|
fn filter_gossip_votes(
|
||||||
|
vote_tracker: &VoteTracker,
|
||||||
|
vote_pubkey: &Pubkey,
|
||||||
|
vote: &Vote,
|
||||||
|
gossip_tx: &Transaction,
|
||||||
|
) -> bool {
|
||||||
|
if vote.slots.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let last_vote_slot = vote.slots.last().unwrap();
|
||||||
|
// Votes from gossip need to be verified as they have not been
|
||||||
|
// verified by the replay pipeline. Determine the authorized voter
|
||||||
|
// based on the last vote slot. This will drop votes from authorized
|
||||||
|
// voters trying to make votes for slots earlier than the epoch for
|
||||||
|
// which they are authorized
|
||||||
|
let actual_authorized_voter =
|
||||||
|
vote_tracker.get_authorized_voter(&vote_pubkey, *last_vote_slot);
|
||||||
|
|
||||||
|
if actual_authorized_voter.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voting without the correct authorized pubkey, dump the vote
|
||||||
|
if !VoteTracker::vote_contains_authorized_voter(
|
||||||
|
&gossip_tx,
|
||||||
|
&actual_authorized_voter.unwrap(),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_and_confirm_with_new_votes(
|
||||||
vote_tracker: &VoteTracker,
|
vote_tracker: &VoteTracker,
|
||||||
gossip_vote_txs: Vec<Transaction>,
|
gossip_vote_txs: Vec<Transaction>,
|
||||||
replayed_votes: Vec<ReplayedVote>,
|
replayed_votes: Vec<ReplayedVote>,
|
||||||
|
@ -662,37 +689,13 @@ impl ClusterInfoVoteListener {
|
||||||
.filter_map(|gossip_tx| {
|
.filter_map(|gossip_tx| {
|
||||||
vote_transaction::parse_vote_transaction(gossip_tx)
|
vote_transaction::parse_vote_transaction(gossip_tx)
|
||||||
.filter(|(vote_pubkey, vote, _)| {
|
.filter(|(vote_pubkey, vote, _)| {
|
||||||
if vote.slots.is_empty() {
|
Self::filter_gossip_votes(vote_tracker, vote_pubkey, vote, gossip_tx)
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let last_vote_slot = vote.slots.last().unwrap();
|
|
||||||
// Votes from gossip need to be verified as they have not been
|
|
||||||
// verified by the replay pipeline. Determine the authorized voter
|
|
||||||
// based on the last vote slot. This will drop votes from authorized
|
|
||||||
// voters trying to make votes for slots earlier than the epoch for
|
|
||||||
// which they are authorized
|
|
||||||
let actual_authorized_voter =
|
|
||||||
vote_tracker.get_authorized_voter(&vote_pubkey, *last_vote_slot);
|
|
||||||
|
|
||||||
if actual_authorized_voter.is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Voting without the correct authorized pubkey, dump the vote
|
|
||||||
if !VoteTracker::vote_contains_authorized_voter(
|
|
||||||
&gossip_tx,
|
|
||||||
&actual_authorized_voter.unwrap(),
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
})
|
||||||
.map(|v| (true, v))
|
.map(|v| (true, v))
|
||||||
})
|
})
|
||||||
.chain(replayed_votes.into_iter().map(|v| (false, v)))
|
.chain(replayed_votes.into_iter().map(|v| (false, v)))
|
||||||
{
|
{
|
||||||
Self::update_new_votes(
|
Self::track_new_votes_and_notify_confirmations(
|
||||||
vote,
|
vote,
|
||||||
&vote_pubkey,
|
&vote_pubkey,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
|
@ -757,7 +760,7 @@ impl ClusterInfoVoteListener {
|
||||||
|
|
||||||
// Returns if the slot was optimistically confirmed, and whether
|
// Returns if the slot was optimistically confirmed, and whether
|
||||||
// the slot was new
|
// the slot was new
|
||||||
fn add_optimistic_confirmation_vote(
|
fn track_optimistic_confirmation_vote(
|
||||||
vote_tracker: &VoteTracker,
|
vote_tracker: &VoteTracker,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
hash: Hash,
|
hash: Hash,
|
||||||
|
@ -909,7 +912,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains_key(&bank.slot()));
|
.contains_key(&bank.slot()));
|
||||||
let bank1 = Bank::new_from_parent(&bank, &Pubkey::default(), bank.slot() + 1);
|
let bank1 = Bank::new_from_parent(&bank, &Pubkey::default(), bank.slot() + 1);
|
||||||
vote_tracker.process_new_root_bank(&bank1);
|
vote_tracker.progress_with_new_root_bank(&bank1);
|
||||||
assert!(!vote_tracker
|
assert!(!vote_tracker
|
||||||
.slot_vote_trackers
|
.slot_vote_trackers
|
||||||
.read()
|
.read()
|
||||||
|
@ -926,7 +929,7 @@ mod tests {
|
||||||
bank.epoch_schedule()
|
bank.epoch_schedule()
|
||||||
.get_first_slot_in_epoch(current_epoch + 1),
|
.get_first_slot_in_epoch(current_epoch + 1),
|
||||||
);
|
);
|
||||||
vote_tracker.process_new_root_bank(&new_epoch_bank);
|
vote_tracker.progress_with_new_root_bank(&new_epoch_bank);
|
||||||
assert!(!vote_tracker.keys.0.read().unwrap().contains(&new_voter));
|
assert!(!vote_tracker.keys.0.read().unwrap().contains(&new_voter));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*vote_tracker.current_epoch.read().unwrap(),
|
*vote_tracker.current_epoch.read().unwrap(),
|
||||||
|
@ -956,7 +959,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
let next_leader_schedule_bank =
|
let next_leader_schedule_bank =
|
||||||
Bank::new_from_parent(&bank, &Pubkey::default(), next_leader_schedule_computed);
|
Bank::new_from_parent(&bank, &Pubkey::default(), next_leader_schedule_computed);
|
||||||
vote_tracker.update_leader_schedule_epoch(&next_leader_schedule_bank);
|
vote_tracker.progress_leader_schedule_epoch(&next_leader_schedule_bank);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
||||||
next_leader_schedule_epoch
|
next_leader_schedule_epoch
|
||||||
|
@ -1007,7 +1010,7 @@ mod tests {
|
||||||
&votes_sender,
|
&votes_sender,
|
||||||
&replay_votes_sender,
|
&replay_votes_sender,
|
||||||
);
|
);
|
||||||
ClusterInfoVoteListener::get_and_process_votes(
|
ClusterInfoVoteListener::listen_and_confirm_votes(
|
||||||
&votes_receiver,
|
&votes_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&bank3,
|
&bank3,
|
||||||
|
@ -1036,7 +1039,7 @@ mod tests {
|
||||||
&votes_sender,
|
&votes_sender,
|
||||||
&replay_votes_sender,
|
&replay_votes_sender,
|
||||||
);
|
);
|
||||||
ClusterInfoVoteListener::get_and_process_votes(
|
ClusterInfoVoteListener::listen_and_confirm_votes(
|
||||||
&votes_receiver,
|
&votes_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&bank3,
|
&bank3,
|
||||||
|
@ -1114,7 +1117,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that all the votes were registered for each validator correctly
|
// Check that all the votes were registered for each validator correctly
|
||||||
ClusterInfoVoteListener::get_and_process_votes(
|
ClusterInfoVoteListener::listen_and_confirm_votes(
|
||||||
&votes_txs_receiver,
|
&votes_txs_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&bank0,
|
&bank0,
|
||||||
|
@ -1233,7 +1236,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and process votes from channel `votes_receiver`
|
// Read and process votes from channel `votes_receiver`
|
||||||
ClusterInfoVoteListener::get_and_process_votes(
|
ClusterInfoVoteListener::listen_and_confirm_votes(
|
||||||
&votes_txs_receiver,
|
&votes_txs_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&bank0,
|
&bank0,
|
||||||
|
@ -1328,7 +1331,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let _ = ClusterInfoVoteListener::get_and_process_votes(
|
let _ = ClusterInfoVoteListener::listen_and_confirm_votes(
|
||||||
&votes_receiver,
|
&votes_receiver,
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
&bank,
|
&bank,
|
||||||
|
@ -1474,7 +1477,7 @@ mod tests {
|
||||||
)];
|
)];
|
||||||
|
|
||||||
let (verified_vote_sender, _verified_vote_receiver) = unbounded();
|
let (verified_vote_sender, _verified_vote_receiver) = unbounded();
|
||||||
ClusterInfoVoteListener::process_votes(
|
ClusterInfoVoteListener::filter_and_confirm_with_new_votes(
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
vote_tx,
|
vote_tx,
|
||||||
// Add gossip vote for same slot, should not affect outcome
|
// Add gossip vote for same slot, should not affect outcome
|
||||||
|
@ -1545,7 +1548,7 @@ mod tests {
|
||||||
|
|
||||||
let new_root_bank =
|
let new_root_bank =
|
||||||
Bank::new_from_parent(&bank, &Pubkey::default(), first_slot_in_new_epoch - 2);
|
Bank::new_from_parent(&bank, &Pubkey::default(), first_slot_in_new_epoch - 2);
|
||||||
ClusterInfoVoteListener::process_votes(
|
ClusterInfoVoteListener::filter_and_confirm_with_new_votes(
|
||||||
&vote_tracker,
|
&vote_tracker,
|
||||||
vote_txs,
|
vote_txs,
|
||||||
vec![(
|
vec![(
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl OptimisticConfirmationVerifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns any optimistic slots that were not rooted
|
// Returns any optimistic slots that were not rooted
|
||||||
pub fn get_unrooted_optimistic_slots(
|
pub fn verify_for_unrooted_optimistic_slots(
|
||||||
&mut self,
|
&mut self,
|
||||||
root_bank: &Bank,
|
root_bank: &Bank,
|
||||||
blockstore: &Blockstore,
|
blockstore: &Blockstore,
|
||||||
|
@ -34,8 +34,8 @@ impl OptimisticConfirmationVerifier {
|
||||||
std::mem::swap(&mut slots_before_root, &mut self.unchecked_slots);
|
std::mem::swap(&mut slots_before_root, &mut self.unchecked_slots);
|
||||||
slots_before_root
|
slots_before_root
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(optimistic_slot, hash)| {
|
.filter(|(optimistic_slot, optimistic_hash)| {
|
||||||
(*optimistic_slot == root && *hash != root_bank.hash())
|
(*optimistic_slot == root && *optimistic_hash != root_bank.hash())
|
||||||
|| (!root_ancestors.contains_key(&optimistic_slot) &&
|
|| (!root_ancestors.contains_key(&optimistic_slot) &&
|
||||||
// In this second part of the `and`, we account for the possibility that
|
// In this second part of the `and`, we account for the possibility that
|
||||||
// there was some other root `rootX` set in BankForks where:
|
// there was some other root `rootX` set in BankForks where:
|
||||||
|
@ -76,6 +76,10 @@ impl OptimisticConfirmationVerifier {
|
||||||
self.last_optimistic_slot_ts = Instant::now();
|
self.last_optimistic_slot_ts = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format_optimistic_confirmd_slot_violation_log(slot: Slot) -> String {
|
||||||
|
format!("Optimistically confirmed slot {} was not rooted", slot)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log_unrooted_optimistic_slots(
|
pub fn log_unrooted_optimistic_slots(
|
||||||
root_bank: &Bank,
|
root_bank: &Bank,
|
||||||
vote_tracker: &VoteTracker,
|
vote_tracker: &VoteTracker,
|
||||||
|
@ -96,7 +100,7 @@ impl OptimisticConfirmationVerifier {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
error!(
|
error!(
|
||||||
"Optimistic slot {} was not rooted,
|
"{},
|
||||||
hash: {},
|
hash: {},
|
||||||
epoch: {},
|
epoch: {},
|
||||||
voted keys: {:?},
|
voted keys: {:?},
|
||||||
|
@ -105,7 +109,7 @@ impl OptimisticConfirmationVerifier {
|
||||||
voted stake: {},
|
voted stake: {},
|
||||||
total epoch stake: {},
|
total epoch stake: {},
|
||||||
pct: {}",
|
pct: {}",
|
||||||
optimistic_slot,
|
Self::format_optimistic_confirmd_slot_violation_log(*optimistic_slot),
|
||||||
hash,
|
hash,
|
||||||
epoch,
|
epoch,
|
||||||
r_slot_tracker
|
r_slot_tracker
|
||||||
|
@ -181,7 +185,8 @@ mod test {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
optimistic_confirmation_verifier.get_unrooted_optimistic_slots(&bank1, &blockstore),
|
optimistic_confirmation_verifier
|
||||||
|
.verify_for_unrooted_optimistic_slots(&bank1, &blockstore),
|
||||||
vec![(1, bad_bank_hash)]
|
vec![(1, bad_bank_hash)]
|
||||||
);
|
);
|
||||||
assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
|
assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
|
||||||
|
@ -228,7 +233,7 @@ mod test {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(optimistic_confirmation_verifier
|
assert!(optimistic_confirmation_verifier
|
||||||
.get_unrooted_optimistic_slots(&bank5, &blockstore)
|
.verify_for_unrooted_optimistic_slots(&bank5, &blockstore)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
// 5 is >= than all the unchecked slots, so should clear everything
|
// 5 is >= than all the unchecked slots, so should clear everything
|
||||||
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
||||||
|
@ -244,7 +249,7 @@ mod test {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(optimistic_confirmation_verifier
|
assert!(optimistic_confirmation_verifier
|
||||||
.get_unrooted_optimistic_slots(&bank3, &blockstore)
|
.verify_for_unrooted_optimistic_slots(&bank3, &blockstore)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
// 3 is bigger than only slot 1, so slot 5 should be left over
|
// 3 is bigger than only slot 1, so slot 5 should be left over
|
||||||
assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
|
assert_eq!(optimistic_confirmation_verifier.unchecked_slots.len(), 1);
|
||||||
|
@ -264,7 +269,8 @@ mod test {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
optimistic_confirmation_verifier.get_unrooted_optimistic_slots(&bank4, &blockstore),
|
optimistic_confirmation_verifier
|
||||||
|
.verify_for_unrooted_optimistic_slots(&bank4, &blockstore),
|
||||||
vec![optimistic_slots[1]]
|
vec![optimistic_slots[1]]
|
||||||
);
|
);
|
||||||
// 4 is bigger than only slots 1 and 3, so slot 5 should be left over
|
// 4 is bigger than only slots 1 and 3, so slot 5 should be left over
|
||||||
|
@ -303,7 +309,8 @@ mod test {
|
||||||
optimistic_confirmation_verifier
|
optimistic_confirmation_verifier
|
||||||
.add_new_optimistic_confirmed_slots(optimistic_slots.clone());
|
.add_new_optimistic_confirmed_slots(optimistic_slots.clone());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
optimistic_confirmation_verifier.get_unrooted_optimistic_slots(&bank7, &blockstore),
|
optimistic_confirmation_verifier
|
||||||
|
.verify_for_unrooted_optimistic_slots(&bank7, &blockstore),
|
||||||
optimistic_slots[0..=1].to_vec()
|
optimistic_slots[0..=1].to_vec()
|
||||||
);
|
);
|
||||||
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
||||||
|
@ -312,7 +319,7 @@ mod test {
|
||||||
blockstore.set_roots(&[1, 3]).unwrap();
|
blockstore.set_roots(&[1, 3]).unwrap();
|
||||||
optimistic_confirmation_verifier.add_new_optimistic_confirmed_slots(optimistic_slots);
|
optimistic_confirmation_verifier.add_new_optimistic_confirmed_slots(optimistic_slots);
|
||||||
assert!(optimistic_confirmation_verifier
|
assert!(optimistic_confirmation_verifier
|
||||||
.get_unrooted_optimistic_slots(&bank7, &blockstore)
|
.verify_for_unrooted_optimistic_slots(&bank7, &blockstore)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
assert!(optimistic_confirmation_verifier.unchecked_slots.is_empty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,17 @@ impl VoteStakeTracker {
|
||||||
&mut self,
|
&mut self,
|
||||||
vote_pubkey: Arc<Pubkey>,
|
vote_pubkey: Arc<Pubkey>,
|
||||||
stake: u64,
|
stake: u64,
|
||||||
total_epoch_stake: u64,
|
total_stake: u64,
|
||||||
) -> (bool, bool) {
|
) -> (bool, bool) {
|
||||||
let is_new = !self.voted.contains(&vote_pubkey);
|
let is_new = !self.voted.contains(&vote_pubkey);
|
||||||
if is_new {
|
if is_new {
|
||||||
self.voted.insert(vote_pubkey);
|
self.voted.insert(vote_pubkey);
|
||||||
let supermajority_stake = (total_epoch_stake as f64 * VOTE_THRESHOLD_SIZE) as u64;
|
let supermajority_stake = (total_stake as f64 * VOTE_THRESHOLD_SIZE) as u64;
|
||||||
let previous_stake = self.stake;
|
let old_stake = self.stake;
|
||||||
self.stake += stake;
|
let new_stake = self.stake + stake;
|
||||||
|
self.stake = new_stake;
|
||||||
(
|
(
|
||||||
previous_stake <= supermajority_stake && self.stake > supermajority_stake,
|
old_stake <= supermajority_stake && supermajority_stake < new_stake,
|
||||||
is_new,
|
is_new,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,6 +11,7 @@ use solana_core::{
|
||||||
cluster_info::VALIDATOR_PORT_RANGE,
|
cluster_info::VALIDATOR_PORT_RANGE,
|
||||||
consensus::{Tower, SWITCH_FORK_THRESHOLD, VOTE_THRESHOLD_DEPTH},
|
consensus::{Tower, SWITCH_FORK_THRESHOLD, VOTE_THRESHOLD_DEPTH},
|
||||||
gossip_service::discover_cluster,
|
gossip_service::discover_cluster,
|
||||||
|
optimistic_confirmation_verifier::OptimisticConfirmationVerifier,
|
||||||
validator::ValidatorConfig,
|
validator::ValidatorConfig,
|
||||||
};
|
};
|
||||||
use solana_download_utils::download_snapshot;
|
use solana_download_utils::download_snapshot;
|
||||||
|
@ -1380,7 +1381,9 @@ fn test_no_voting() {
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_optimistic_confirmation_violation_detection() {
|
fn test_optimistic_confirmation_violation_detection() {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let mut buf = BufferRedirect::stderr().unwrap();
|
let buf = std::env::var("OPTIMISTIC_CONF_TEST_DUMP_LOG")
|
||||||
|
.err()
|
||||||
|
.map(|_| BufferRedirect::stderr().unwrap());
|
||||||
// First set up the cluster with 2 nodes
|
// First set up the cluster with 2 nodes
|
||||||
let slots_per_epoch = 2048;
|
let slots_per_epoch = 2048;
|
||||||
let node_stakes = vec![51, 50];
|
let node_stakes = vec![51, 50];
|
||||||
|
@ -1467,10 +1470,17 @@ fn test_optimistic_confirmation_violation_detection() {
|
||||||
|
|
||||||
// Check to see that validator detected optimistic confirmation for
|
// Check to see that validator detected optimistic confirmation for
|
||||||
// `prev_voted_slot` failed
|
// `prev_voted_slot` failed
|
||||||
let expected_log = format!("Optimistic slot {} was not rooted", prev_voted_slot);
|
let expected_log =
|
||||||
let mut output = String::new();
|
OptimisticConfirmationVerifier::format_optimistic_confirmd_slot_violation_log(
|
||||||
buf.read_to_string(&mut output).unwrap();
|
prev_voted_slot,
|
||||||
assert!(output.contains(&expected_log));
|
);
|
||||||
|
if let Some(mut buf) = buf {
|
||||||
|
let mut output = String::new();
|
||||||
|
buf.read_to_string(&mut output).unwrap();
|
||||||
|
assert!(output.contains(&expected_log));
|
||||||
|
} else {
|
||||||
|
panic!("dumped log and disaled testing");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue