Add hook for getting vote transactions on replay (#11264)

* Add hook for getting vote transactions on replay

Co-authored-by: Carl <carl@solana.com>
This commit is contained in:
carllin
2020-07-29 23:17:40 -07:00
committed by GitHub
parent a888f2f516
commit bf18524368
11 changed files with 426 additions and 228 deletions

View File

@@ -287,7 +287,7 @@ fn simulate_process_entries(
hash: next_hash(&bank.last_blockhash(), 1, &tx_vector), hash: next_hash(&bank.last_blockhash(), 1, &tx_vector),
transactions: tx_vector, transactions: tx_vector,
}; };
process_entries(&bank, &[entry], randomize_txs, None).unwrap(); process_entries(&bank, &[entry], randomize_txs, None, None).unwrap();
} }
fn bench_process_entries(randomize_txs: bool, bencher: &mut Bencher) { fn bench_process_entries(randomize_txs: bool, bencher: &mut Bencher) {

View File

@@ -1,11 +1,9 @@
use crate::{ use crate::{
cluster_info::{ClusterInfo, GOSSIP_SLEEP_MILLIS}, cluster_info::{ClusterInfo, GOSSIP_SLEEP_MILLIS},
consensus::PubkeyVotes,
crds_value::CrdsValueLabel, crds_value::CrdsValueLabel,
optimistic_confirmation_verifier::OptimisticConfirmationVerifier, optimistic_confirmation_verifier::OptimisticConfirmationVerifier,
poh_recorder::PohRecorder, poh_recorder::PohRecorder,
pubkey_references::LockedPubkeyReferences, pubkey_references::LockedPubkeyReferences,
replay_stage::ReplayVotesReceiver,
result::{Error, Result}, result::{Error, Result},
rpc_subscriptions::RpcSubscriptions, rpc_subscriptions::RpcSubscriptions,
sigverify, sigverify,
@@ -17,7 +15,10 @@ use crossbeam_channel::{
}; };
use itertools::izip; use itertools::izip;
use log::*; use log::*;
use solana_ledger::blockstore::Blockstore; use solana_ledger::{
blockstore::Blockstore,
blockstore_processor::{ReplayVotesReceiver, ReplayedVote},
};
use solana_metrics::inc_new_counter_debug; use solana_metrics::inc_new_counter_debug;
use solana_perf::packet::{self, Packets}; use solana_perf::packet::{self, Packets};
use solana_runtime::{ use solana_runtime::{
@@ -34,9 +35,9 @@ use solana_sdk::{
pubkey::Pubkey, pubkey::Pubkey,
transaction::Transaction, transaction::Transaction,
}; };
use solana_vote_program::{self, vote_transaction}; use solana_vote_program::{self, vote_state::Vote, vote_transaction};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::HashMap,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
{Arc, Mutex, RwLock}, {Arc, Mutex, RwLock},
@@ -417,7 +418,7 @@ impl ClusterInfoVoteListener {
fn process_votes_loop( fn process_votes_loop(
exit: Arc<AtomicBool>, exit: Arc<AtomicBool>,
vote_txs_receiver: VerifiedVoteTransactionsReceiver, gossip_vote_txs_receiver: VerifiedVoteTransactionsReceiver,
vote_tracker: Arc<VoteTracker>, vote_tracker: Arc<VoteTracker>,
bank_forks: Arc<RwLock<BankForks>>, bank_forks: Arc<RwLock<BankForks>>,
subscriptions: Arc<RpcSubscriptions>, subscriptions: Arc<RpcSubscriptions>,
@@ -449,7 +450,7 @@ impl ClusterInfoVoteListener {
last_process_root = Instant::now(); last_process_root = Instant::now();
} }
let optimistic_confirmed_slots = Self::get_and_process_votes( let optimistic_confirmed_slots = Self::get_and_process_votes(
&vote_txs_receiver, &gossip_vote_txs_receiver,
&vote_tracker, &vote_tracker,
&root_bank, &root_bank,
&subscriptions, &subscriptions,
@@ -475,7 +476,7 @@ impl ClusterInfoVoteListener {
#[cfg(test)] #[cfg(test)]
pub fn get_and_process_votes_for_tests( pub fn get_and_process_votes_for_tests(
vote_txs_receiver: &VerifiedVoteTransactionsReceiver, gossip_vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
root_bank: &Bank, root_bank: &Bank,
subscriptions: &RpcSubscriptions, subscriptions: &RpcSubscriptions,
@@ -483,7 +484,7 @@ impl ClusterInfoVoteListener {
replay_votes_receiver: &ReplayVotesReceiver, replay_votes_receiver: &ReplayVotesReceiver,
) -> Result<Vec<(Slot, Hash)>> { ) -> Result<Vec<(Slot, Hash)>> {
Self::get_and_process_votes( Self::get_and_process_votes(
vote_txs_receiver, gossip_vote_txs_receiver,
vote_tracker, vote_tracker,
root_bank, root_bank,
subscriptions, subscriptions,
@@ -493,7 +494,7 @@ impl ClusterInfoVoteListener {
} }
fn get_and_process_votes( fn get_and_process_votes(
vote_txs_receiver: &VerifiedVoteTransactionsReceiver, gossip_vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
root_bank: &Bank, root_bank: &Bank,
subscriptions: &RpcSubscriptions, subscriptions: &RpcSubscriptions,
@@ -501,7 +502,7 @@ impl ClusterInfoVoteListener {
replay_votes_receiver: &ReplayVotesReceiver, replay_votes_receiver: &ReplayVotesReceiver,
) -> Result<Vec<(Slot, Hash)>> { ) -> Result<Vec<(Slot, Hash)>> {
let mut sel = Select::new(); let mut sel = Select::new();
sel.recv(vote_txs_receiver); sel.recv(gossip_vote_txs_receiver);
sel.recv(replay_votes_receiver); sel.recv(replay_votes_receiver);
let mut remaining_wait_time = 200; let mut remaining_wait_time = 200;
loop { loop {
@@ -517,16 +518,16 @@ impl ClusterInfoVoteListener {
// Should not early return from this point onwards until `process_votes()` // Should not early return from this point onwards until `process_votes()`
// returns below to avoid missing any potential `optimistic_confirmed_slots` // returns below to avoid missing any potential `optimistic_confirmed_slots`
let vote_txs: Vec<_> = 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 !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::process_votes(
vote_tracker, vote_tracker,
vote_txs, gossip_vote_txs,
replay_votes,
root_bank, root_bank,
subscriptions, subscriptions,
verified_vote_sender, verified_vote_sender,
&replay_votes,
)); ));
} else { } else {
remaining_wait_time = remaining_wait_time remaining_wait_time = remaining_wait_time
@@ -536,49 +537,28 @@ impl ClusterInfoVoteListener {
Ok(vec![]) Ok(vec![])
} }
fn process_votes( #[allow(clippy::too_many_arguments)]
fn update_new_votes(
vote: Vote,
vote_pubkey: &Pubkey,
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
vote_txs: Vec<Transaction>,
root_bank: &Bank, root_bank: &Bank,
subscriptions: &RpcSubscriptions, subscriptions: &RpcSubscriptions,
verified_vote_sender: &VerifiedVoteSender, verified_vote_sender: &VerifiedVoteSender,
replay_votes: &[Arc<PubkeyVotes>], diff: &mut HashMap<Slot, HashMap<Arc<Pubkey>, bool>>,
) -> Vec<(Slot, Hash)> { new_optimistic_confirmed_slots: &mut Vec<(Slot, Hash)>,
let mut optimistic_confirmation_counted: HashSet<(Slot, Pubkey)> = HashSet::new(); is_gossip_vote: bool,
let mut diff: HashMap<Slot, HashMap<Arc<Pubkey>, bool>> = HashMap::new(); ) {
let mut new_optimistic_confirmed_slots = vec![];
let root = root_bank.slot();
{
for tx in vote_txs {
if let Some((vote_pubkey, vote, _)) = vote_transaction::parse_vote_transaction(&tx)
{
if vote.slots.is_empty() { if vote.slots.is_empty() {
continue; return;
} }
let last_vote_slot = vote.slots.last().unwrap(); let last_vote_slot = vote.slots.last().unwrap();
// 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() {
continue;
}
// Voting without the correct authorized pubkey, dump the vote
if !VoteTracker::vote_contains_authorized_voter(
&tx,
&actual_authorized_voter.unwrap(),
) {
continue;
}
let root = root_bank.slot(); let root = root_bank.slot();
let last_vote_hash = vote.hash; let last_vote_hash = vote.hash;
for slot in &vote.slots { let mut is_new_vote = false;
for slot in vote.slots.iter().rev() {
// If slot is before the root, or so far ahead we don't have // If slot is before the root, or so far ahead we don't have
// stake information, then ignore it // stake information, then ignore it
let epoch = root_bank.epoch_schedule().get_epoch(*slot); let epoch = root_bank.epoch_schedule().get_epoch(*slot);
@@ -589,10 +569,9 @@ impl ClusterInfoVoteListener {
let epoch_stakes = epoch_stakes.unwrap(); let epoch_stakes = epoch_stakes.unwrap();
let epoch_vote_accounts = Stakes::vote_accounts(epoch_stakes.stakes()); let epoch_vote_accounts = Stakes::vote_accounts(epoch_stakes.stakes());
let total_epoch_stake = epoch_stakes.total_stake(); 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 { let update_optimistic_confirmation_info = if slot == last_vote_slot {
let stake = epoch_vote_accounts let stake = epoch_vote_accounts
@@ -609,47 +588,110 @@ impl ClusterInfoVoteListener {
// 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.
if !optimistic_confirmation_counted let (is_confirmed, is_new) = Self::add_optimistic_confirmation_vote(
.contains(&(*slot, *unduplicated_pubkey))
&& Self::add_optimistic_confirmation_vote(
vote_tracker, vote_tracker,
*slot, *slot,
hash, hash,
unduplicated_pubkey.clone(), unduplicated_pubkey.clone(),
stake, stake,
total_epoch_stake, total_epoch_stake,
) );
{
optimistic_confirmation_counted if is_confirmed {
.insert((*slot, *unduplicated_pubkey));
new_optimistic_confirmed_slots.push((*slot, last_vote_hash)); new_optimistic_confirmed_slots.push((*slot, last_vote_hash));
// TODO: Notify subscribers about new optimistic confirmation // TODO: Notify subscribers about new optimistic confirmation
} }
if !is_new && !is_gossip_vote {
// By now:
// 1) The vote must have come from ReplayStage,
// 2) We've seen this vote from replay for this hash before
// (`add_optimistic_confirmation_vote()` will not set `is_new == true`
// for same slot different hash), so short circuit because this vote
// has no new information
// Note gossip votes will always be processed because those should be unique
// and we need to update the gossip-only stake in the `VoteTracker`.
return;
} }
diff.entry(*slot) is_new_vote = is_new;
.or_default()
.insert(unduplicated_pubkey, true);
} }
subscriptions.notify_vote(&vote);
let _ = verified_vote_sender.send((vote_pubkey, vote.slots));
}
}
}
// Process the replay votes
for votes in replay_votes {
for (pubkey, slot) in votes.iter() {
if *slot <= root {
continue;
}
let unduplicated_pubkey = vote_tracker.keys.get_or_insert(pubkey);
diff.entry(*slot) diff.entry(*slot)
.or_default() .or_default()
.entry(unduplicated_pubkey) .entry(unduplicated_pubkey)
.or_default(); .and_modify(|seen_in_gossip_previously| {
*seen_in_gossip_previously = *seen_in_gossip_previously || is_gossip_vote
})
.or_insert(is_gossip_vote);
} }
if is_new_vote {
subscriptions.notify_vote(&vote);
let _ = verified_vote_sender.send((*vote_pubkey, vote.slots));
}
}
fn process_votes(
vote_tracker: &VoteTracker,
gossip_vote_txs: Vec<Transaction>,
replayed_votes: Vec<ReplayedVote>,
root_bank: &Bank,
subscriptions: &RpcSubscriptions,
verified_vote_sender: &VerifiedVoteSender,
) -> Vec<(Slot, Hash)> {
let mut diff: HashMap<Slot, HashMap<Arc<Pubkey>, bool>> = HashMap::new();
let mut new_optimistic_confirmed_slots = vec![];
// Process votes from gossip and ReplayStage
for (i, (vote_pubkey, vote, _)) in gossip_vote_txs
.iter()
.filter_map(|gossip_tx| {
vote_transaction::parse_vote_transaction(gossip_tx).filter(
|(vote_pubkey, vote, _)| {
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
},
)
})
.chain(replayed_votes)
.enumerate()
{
Self::update_new_votes(
vote,
&vote_pubkey,
&vote_tracker,
root_bank,
subscriptions,
verified_vote_sender,
&mut diff,
&mut new_optimistic_confirmed_slots,
i < gossip_vote_txs.len(),
);
} }
// Process all the slots accumulated from replay and gossip. // Process all the slots accumulated from replay and gossip.
@@ -661,13 +703,6 @@ impl ClusterInfoVoteListener {
slot_diff.retain(|pubkey, seen_in_gossip_above| { slot_diff.retain(|pubkey, seen_in_gossip_above| {
let seen_in_gossip_previously = r_slot_tracker.voted.get(pubkey); let seen_in_gossip_previously = r_slot_tracker.voted.get(pubkey);
let is_new = seen_in_gossip_previously.is_none(); let is_new = seen_in_gossip_previously.is_none();
if is_new && !*seen_in_gossip_above {
// If this vote wasn't seen in gossip, then it must be a
// replay vote, and we haven't sent a notification for
// those yet
let _ = verified_vote_sender.send((**pubkey, vec![slot]));
}
// `is_new_from_gossip` means we observed a vote for this slot // `is_new_from_gossip` means we observed a vote for this slot
// for the first time in gossip // for the first time in gossip
let is_new_from_gossip = !seen_in_gossip_previously.cloned().unwrap_or(false) let is_new_from_gossip = !seen_in_gossip_previously.cloned().unwrap_or(false)
@@ -721,7 +756,8 @@ impl ClusterInfoVoteListener {
new_optimistic_confirmed_slots new_optimistic_confirmed_slots
} }
// Returns if the slot was optimistically confirmed // Returns if the slot was optimistically confirmed, and whether
// the slot was new
fn add_optimistic_confirmation_vote( fn add_optimistic_confirmation_vote(
vote_tracker: &VoteTracker, vote_tracker: &VoteTracker,
slot: Slot, slot: Slot,
@@ -729,7 +765,7 @@ impl ClusterInfoVoteListener {
pubkey: Arc<Pubkey>, pubkey: Arc<Pubkey>,
stake: u64, stake: u64,
total_epoch_stake: u64, total_epoch_stake: u64,
) -> bool { ) -> (bool, bool) {
let slot_tracker = vote_tracker.get_or_insert_slot_tracker(slot); let slot_tracker = vote_tracker.get_or_insert_slot_tracker(slot);
// Insert vote and check for optimistic confirmation // Insert vote and check for optimistic confirmation
let mut w_slot_tracker = slot_tracker.write().unwrap(); let mut w_slot_tracker = slot_tracker.write().unwrap();
@@ -784,16 +820,18 @@ impl ClusterInfoVoteListener {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::replay_stage::ReplayVotesSender; use solana_ledger::blockstore_processor::ReplayVotesSender;
use solana_perf::packet; use solana_perf::packet;
use solana_runtime::{ use solana_runtime::{
bank::Bank, bank::Bank,
commitment::BlockCommitmentCache, commitment::BlockCommitmentCache,
genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs}, genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs},
}; };
use solana_sdk::hash::Hash; use solana_sdk::{
use solana_sdk::signature::Signature; hash::Hash,
use solana_sdk::signature::{Keypair, Signer}; signature::{Keypair, Signature, Signer},
};
use solana_vote_program::vote_state::Vote;
use std::collections::BTreeSet; use std::collections::BTreeSet;
#[test] #[test]
@@ -1048,7 +1086,7 @@ mod tests {
gossip_vote_slots: Vec<Slot>, gossip_vote_slots: Vec<Slot>,
replay_vote_slots: Vec<Slot>, replay_vote_slots: Vec<Slot>,
validator_voting_keypairs: &[ValidatorVoteKeypairs], validator_voting_keypairs: &[ValidatorVoteKeypairs],
hash: Option<Hash>, switch_proof_hash: Option<Hash>,
votes_sender: &VerifiedVoteTransactionsSender, votes_sender: &VerifiedVoteTransactionsSender,
replay_votes_sender: &ReplayVotesSender, replay_votes_sender: &ReplayVotesSender,
) { ) {
@@ -1062,16 +1100,18 @@ mod tests {
node_keypair, node_keypair,
vote_keypair, vote_keypair,
vote_keypair, vote_keypair,
hash, switch_proof_hash,
); );
votes_sender.send(vec![vote_tx]).unwrap(); votes_sender.send(vec![vote_tx]).unwrap();
for vote_slot in &replay_vote_slots { let replay_vote = Vote::new(replay_vote_slots.clone(), Hash::default());
// Send twice, should only expect to be notified once later // Send same vote twice, but should only notify once
for _ in 0..2 {
replay_votes_sender replay_votes_sender
.send(Arc::new(vec![(vote_keypair.pubkey(), *vote_slot)])) .send((
.unwrap(); vote_keypair.pubkey(),
replay_votes_sender replay_vote.clone(),
.send(Arc::new(vec![(vote_keypair.pubkey(), *vote_slot)])) switch_proof_hash,
))
.unwrap(); .unwrap();
} }
}); });
@@ -1155,7 +1195,7 @@ mod tests {
// the `optimistic` vote set. // the `optimistic` vote set.
let optimistic_votes_tracker = let optimistic_votes_tracker =
r_slot_vote_tracker.optimistic_votes_tracker(&Hash::default()); r_slot_vote_tracker.optimistic_votes_tracker(&Hash::default());
if vote_slot == 2 { if vote_slot == 2 || vote_slot == 4 {
let optimistic_votes_tracker = optimistic_votes_tracker.unwrap(); let optimistic_votes_tracker = optimistic_votes_tracker.unwrap();
assert!(optimistic_votes_tracker.voted().contains(&pubkey)); assert!(optimistic_votes_tracker.voted().contains(&pubkey));
assert_eq!( assert_eq!(
@@ -1269,7 +1309,7 @@ mod tests {
} }
} }
fn run_test_process_votes3(hash: Option<Hash>) { fn run_test_process_votes3(switch_proof_hash: Option<Hash>) {
let (votes_sender, votes_receiver) = unbounded(); let (votes_sender, votes_receiver) = unbounded();
let (verified_vote_sender, _verified_vote_receiver) = unbounded(); let (verified_vote_sender, _verified_vote_receiver) = unbounded();
let (replay_votes_sender, replay_votes_receiver) = unbounded(); let (replay_votes_sender, replay_votes_receiver) = unbounded();
@@ -1288,6 +1328,7 @@ mod tests {
vec![2], vec![2],
vec![0, 1, 2], vec![0, 1, 2],
vec![1, 0, 2], vec![1, 0, 2],
vec![0, 1, 2, 0, 1, 2],
]; ];
for events in ordered_events { for events in ordered_events {
let (vote_tracker, bank, validator_voting_keypairs, subscriptions) = setup(); let (vote_tracker, bank, validator_voting_keypairs, subscriptions) = setup();
@@ -1303,13 +1344,17 @@ mod tests {
node_keypair, node_keypair,
vote_keypair, vote_keypair,
vote_keypair, vote_keypair,
hash, switch_proof_hash,
); );
votes_sender.send(vec![vote_tx.clone()]).unwrap(); votes_sender.send(vec![vote_tx.clone()]).unwrap();
} }
if e == 1 || e == 2 { if e == 1 || e == 2 {
replay_votes_sender replay_votes_sender
.send(Arc::new(vec![(vote_keypair.pubkey(), vote_slot)])) .send((
vote_keypair.pubkey(),
Vote::new(vec![vote_slot], Hash::default()),
switch_proof_hash,
))
.unwrap(); .unwrap();
} }
let _ = ClusterInfoVoteListener::get_and_process_votes( let _ = ClusterInfoVoteListener::get_and_process_votes(
@@ -1403,9 +1448,6 @@ mod tests {
// SlotVoteTracker.voted, one in SlotVoteTracker.updates, one in // SlotVoteTracker.voted, one in SlotVoteTracker.updates, one in
// SlotVoteTracker.optimistic_votes_tracker // SlotVoteTracker.optimistic_votes_tracker
let ref_count_per_vote = 3; let ref_count_per_vote = 3;
// Replay votes don't get added to `SlotVoteTracker.optimistic_votes_tracker`,
// so there's one less
let ref_count_per_replay_vote = ref_count_per_vote - 1;
let ref_count_per_new_key = 1; let ref_count_per_new_key = 1;
// Create some voters at genesis // Create some voters at genesis
@@ -1448,14 +1490,15 @@ mod tests {
ClusterInfoVoteListener::process_votes( ClusterInfoVoteListener::process_votes(
&vote_tracker, &vote_tracker,
vote_tx, vote_tx,
// Add gossip vote for same slot, should not affect outcome
vec![(
validator0_keypairs.vote_keypair.pubkey(),
Vote::new(vec![voted_slot], Hash::default()),
None,
)],
&bank, &bank,
&subscriptions, &subscriptions,
&verified_vote_sender, &verified_vote_sender,
// Add vote for same slot, should not affect outcome
&[Arc::new(vec![(
validator0_keypairs.vote_keypair.pubkey(),
voted_slot,
)])],
); );
let ref_count = Arc::strong_count( let ref_count = Arc::strong_count(
&vote_tracker &vote_tracker
@@ -1517,13 +1560,14 @@ mod tests {
ClusterInfoVoteListener::process_votes( ClusterInfoVoteListener::process_votes(
&vote_tracker, &vote_tracker,
vote_txs, vote_txs,
vec![(
validator_keypairs[1].vote_keypair.pubkey(),
Vote::new(vec![first_slot_in_new_epoch], Hash::default()),
None,
)],
&new_root_bank, &new_root_bank,
&subscriptions, &subscriptions,
&verified_vote_sender, &verified_vote_sender,
&[Arc::new(vec![(
validator_keypairs[1].vote_keypair.pubkey(),
first_slot_in_new_epoch,
)])],
); );
// Check new replay vote pubkey first // Check new replay vote pubkey first
@@ -1540,7 +1584,7 @@ mod tests {
// `ref_count_per_optimistic_vote + ref_count_per_new_key`. // `ref_count_per_optimistic_vote + ref_count_per_new_key`.
// +ref_count_per_new_key for the new pubkey in `vote_tracker.keys` and // +ref_count_per_new_key for the new pubkey in `vote_tracker.keys` and
// +ref_count_per_optimistic_vote for the one new vote // +ref_count_per_optimistic_vote for the one new vote
assert_eq!(ref_count, ref_count_per_replay_vote + ref_count_per_new_key); assert_eq!(ref_count, ref_count_per_vote + ref_count_per_new_key);
// Check the existing pubkey // Check the existing pubkey
let ref_count = Arc::strong_count( let ref_count = Arc::strong_count(

View File

@@ -679,7 +679,6 @@ pub mod test {
progress_map::ForkProgress, progress_map::ForkProgress,
replay_stage::{HeaviestForkFailures, ReplayStage}, replay_stage::{HeaviestForkFailures, ReplayStage},
}; };
use crossbeam_channel::unbounded;
use solana_runtime::{ use solana_runtime::{
bank::Bank, bank::Bank,
bank_forks::BankForks, bank_forks::BankForks,
@@ -795,7 +794,6 @@ pub mod test {
.cloned() .cloned()
.collect(); .collect();
let (replay_slot_sender, _replay_slot_receiver) = unbounded();
let _ = ReplayStage::compute_bank_stats( let _ = ReplayStage::compute_bank_stats(
&my_pubkey, &my_pubkey,
&ancestors, &ancestors,
@@ -808,7 +806,6 @@ pub mod test {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut self.heaviest_subtree_fork_choice, &mut self.heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_slot_sender,
); );
let vote_bank = self let vote_bank = self

View File

@@ -7,7 +7,7 @@ use crate::{
cluster_info_vote_listener::VoteTracker, cluster_info_vote_listener::VoteTracker,
cluster_slots::ClusterSlots, cluster_slots::ClusterSlots,
commitment_service::{AggregateCommitmentService, CommitmentAggregationData}, commitment_service::{AggregateCommitmentService, CommitmentAggregationData},
consensus::{ComputedBankState, PubkeyVotes, Stake, SwitchForkDecision, Tower, VotedStakes}, consensus::{ComputedBankState, Stake, SwitchForkDecision, Tower, VotedStakes},
fork_choice::{ForkChoice, SelectVoteAndResetForkResult}, fork_choice::{ForkChoice, SelectVoteAndResetForkResult},
heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice, heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice,
poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS}, poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
@@ -18,11 +18,12 @@ use crate::{
rewards_recorder_service::RewardsRecorderSender, rewards_recorder_service::RewardsRecorderSender,
rpc_subscriptions::RpcSubscriptions, rpc_subscriptions::RpcSubscriptions,
}; };
use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
use solana_ledger::{ use solana_ledger::{
block_error::BlockError, block_error::BlockError,
blockstore::Blockstore, blockstore::Blockstore,
blockstore_processor::{self, BlockstoreProcessorError, TransactionStatusSender}, blockstore_processor::{
self, BlockstoreProcessorError, ReplayVotesSender, TransactionStatusSender,
},
entry::VerifyRecyclers, entry::VerifyRecyclers,
leader_schedule_cache::LeaderScheduleCache, leader_schedule_cache::LeaderScheduleCache,
}; };
@@ -62,9 +63,6 @@ pub const MAX_ENTRY_RECV_PER_ITER: usize = 512;
pub const SUPERMINORITY_THRESHOLD: f64 = 1f64 / 3f64; pub const SUPERMINORITY_THRESHOLD: f64 = 1f64 / 3f64;
pub const MAX_UNCONFIRMED_SLOTS: usize = 5; pub const MAX_UNCONFIRMED_SLOTS: usize = 5;
pub type ReplayVotesSender = CrossbeamSender<Arc<PubkeyVotes>>;
pub type ReplayVotesReceiver = CrossbeamReceiver<Arc<PubkeyVotes>>;
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub(crate) enum HeaviestForkFailures { pub(crate) enum HeaviestForkFailures {
LockedOut(u64), LockedOut(u64),
@@ -346,6 +344,7 @@ impl ReplayStage {
&verify_recyclers, &verify_recyclers,
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&subscriptions, &subscriptions,
&replay_votes_sender,
); );
replay_active_banks_time.stop(); replay_active_banks_time.stop();
Self::report_memory(&allocated, "replay_active_banks", start); Self::report_memory(&allocated, "replay_active_banks", start);
@@ -392,7 +391,6 @@ impl ReplayStage {
&mut all_pubkeys, &mut all_pubkeys,
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&mut bank_weight_fork_choice, &mut bank_weight_fork_choice,
&replay_votes_sender,
); );
compute_bank_stats_time.stop(); compute_bank_stats_time.stop();
@@ -942,6 +940,7 @@ impl ReplayStage {
blockstore: &Blockstore, blockstore: &Blockstore,
bank_progress: &mut ForkProgress, bank_progress: &mut ForkProgress,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: &ReplayVotesSender,
verify_recyclers: &VerifyRecyclers, verify_recyclers: &VerifyRecyclers,
) -> result::Result<usize, BlockstoreProcessorError> { ) -> result::Result<usize, BlockstoreProcessorError> {
let tx_count_before = bank_progress.replay_progress.num_txs; let tx_count_before = bank_progress.replay_progress.num_txs;
@@ -952,6 +951,7 @@ impl ReplayStage {
&mut bank_progress.replay_progress, &mut bank_progress.replay_progress,
false, false,
transaction_status_sender, transaction_status_sender,
Some(replay_votes_sender),
None, None,
verify_recyclers, verify_recyclers,
); );
@@ -1203,6 +1203,7 @@ impl ReplayStage {
); );
} }
#[allow(clippy::too_many_arguments)]
fn replay_active_banks( fn replay_active_banks(
blockstore: &Arc<Blockstore>, blockstore: &Arc<Blockstore>,
bank_forks: &Arc<RwLock<BankForks>>, bank_forks: &Arc<RwLock<BankForks>>,
@@ -1213,6 +1214,7 @@ impl ReplayStage {
verify_recyclers: &VerifyRecyclers, verify_recyclers: &VerifyRecyclers,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice, heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
subscriptions: &Arc<RpcSubscriptions>, subscriptions: &Arc<RpcSubscriptions>,
replay_votes_sender: &ReplayVotesSender,
) -> bool { ) -> bool {
let mut did_complete_bank = false; let mut did_complete_bank = false;
let mut tx_count = 0; let mut tx_count = 0;
@@ -1258,6 +1260,7 @@ impl ReplayStage {
&blockstore, &blockstore,
bank_progress, bank_progress,
transaction_status_sender.clone(), transaction_status_sender.clone(),
replay_votes_sender,
verify_recyclers, verify_recyclers,
); );
match replay_result { match replay_result {
@@ -1309,7 +1312,6 @@ impl ReplayStage {
all_pubkeys: &mut PubkeyReferences, all_pubkeys: &mut PubkeyReferences,
heaviest_subtree_fork_choice: &mut dyn ForkChoice, heaviest_subtree_fork_choice: &mut dyn ForkChoice,
bank_weight_fork_choice: &mut dyn ForkChoice, bank_weight_fork_choice: &mut dyn ForkChoice,
replay_votes_sender: &ReplayVotesSender,
) -> Vec<Slot> { ) -> Vec<Slot> {
frozen_banks.sort_by_key(|bank| bank.slot()); frozen_banks.sort_by_key(|bank| bank.slot());
let mut new_stats = vec![]; let mut new_stats = vec![];
@@ -1333,7 +1335,6 @@ impl ReplayStage {
); );
// Notify any listeners of the votes found in this newly computed // Notify any listeners of the votes found in this newly computed
// bank // bank
let _ = replay_votes_sender.send(computed_bank_state.pubkey_votes.clone());
heaviest_subtree_fork_choice.compute_bank_stats( heaviest_subtree_fork_choice.compute_bank_stats(
&bank, &bank,
tower, tower,
@@ -2414,6 +2415,7 @@ pub(crate) mod tests {
F: Fn(&Keypair, Arc<Bank>) -> Vec<Shred>, F: Fn(&Keypair, Arc<Bank>) -> Vec<Shred>,
{ {
let ledger_path = get_tmp_ledger_path!(); let ledger_path = get_tmp_ledger_path!();
let (replay_votes_sender, _replay_votes_receiver) = unbounded();
let res = { let res = {
let blockstore = Arc::new( let blockstore = Arc::new(
Blockstore::open(&ledger_path) Blockstore::open(&ledger_path)
@@ -2438,6 +2440,7 @@ pub(crate) mod tests {
&blockstore, &blockstore,
&mut bank0_progress, &mut bank0_progress,
None, None,
&replay_votes_sender,
&VerifyRecyclers::default(), &VerifyRecyclers::default(),
); );
@@ -2601,6 +2604,7 @@ pub(crate) mod tests {
blockstore.set_roots(&[slot]).unwrap(); blockstore.set_roots(&[slot]).unwrap();
let (transaction_status_sender, transaction_status_receiver) = unbounded(); let (transaction_status_sender, transaction_status_receiver) = unbounded();
let (replay_votes_sender, _replay_votes_receiver) = unbounded();
let transaction_status_service = TransactionStatusService::new( let transaction_status_service = TransactionStatusService::new(
transaction_status_receiver, transaction_status_receiver,
blockstore, blockstore,
@@ -2614,6 +2618,7 @@ pub(crate) mod tests {
&entries, &entries,
true, true,
Some(transaction_status_sender), Some(transaction_status_sender),
Some(&replay_votes_sender),
); );
transaction_status_service.join().unwrap(); transaction_status_service.join().unwrap();
@@ -2724,7 +2729,6 @@ pub(crate) mod tests {
.cloned() .cloned()
.collect(); .collect();
let tower = Tower::new_for_tests(0, 0.67); let tower = Tower::new_for_tests(0, 0.67);
let (replay_votes_sender, replay_votes_receiver) = unbounded();
let newly_computed = ReplayStage::compute_bank_stats( let newly_computed = ReplayStage::compute_bank_stats(
&node_pubkey, &node_pubkey,
&ancestors, &ancestors,
@@ -2737,11 +2741,9 @@ pub(crate) mod tests {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_votes_sender,
); );
// bank 0 has no votes, should not send any votes on the channel // bank 0 has no votes, should not send any votes on the channel
assert_eq!(replay_votes_receiver.try_recv().unwrap(), Arc::new(vec![]));
assert_eq!(newly_computed, vec![0]); assert_eq!(newly_computed, vec![0]);
// The only vote is in bank 1, and bank_forks does not currently contain // The only vote is in bank 1, and bank_forks does not currently contain
@@ -2785,15 +2787,9 @@ pub(crate) mod tests {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_votes_sender,
); );
// Bank 1 had one vote, ensure that `compute_bank_stats` notifies listeners // Bank 1 had one vote
// via `replay_votes_receiver`.
assert_eq!(
replay_votes_receiver.try_recv().unwrap(),
Arc::new(vec![(my_keypairs.vote_keypair.pubkey(), 0)])
);
assert_eq!(newly_computed, vec![1]); assert_eq!(newly_computed, vec![1]);
{ {
let fork_progress = progress.get(&1).unwrap(); let fork_progress = progress.get(&1).unwrap();
@@ -2827,10 +2823,8 @@ pub(crate) mod tests {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_votes_sender,
); );
// No new stats should have been computed // No new stats should have been computed
assert!(replay_votes_receiver.try_iter().next().is_none());
assert!(newly_computed.is_empty()); assert!(newly_computed.is_empty());
} }
@@ -2855,7 +2849,6 @@ pub(crate) mod tests {
.collect(); .collect();
let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors(); let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
let (replay_votes_sender, _replay_votes_receiver) = unbounded();
ReplayStage::compute_bank_stats( ReplayStage::compute_bank_stats(
&node_pubkey, &node_pubkey,
&ancestors, &ancestors,
@@ -2868,7 +2861,6 @@ pub(crate) mod tests {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut heaviest_subtree_fork_choice, &mut heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_votes_sender,
); );
assert_eq!( assert_eq!(
@@ -2931,7 +2923,6 @@ pub(crate) mod tests {
.cloned() .cloned()
.collect(); .collect();
let (replay_votes_sender, _replay_votes_receiver) = unbounded();
ReplayStage::compute_bank_stats( ReplayStage::compute_bank_stats(
&node_pubkey, &node_pubkey,
&vote_simulator.bank_forks.read().unwrap().ancestors(), &vote_simulator.bank_forks.read().unwrap().ancestors(),
@@ -2944,7 +2935,6 @@ pub(crate) mod tests {
&mut PubkeyReferences::default(), &mut PubkeyReferences::default(),
&mut vote_simulator.heaviest_subtree_fork_choice, &mut vote_simulator.heaviest_subtree_fork_choice,
&mut BankWeightForkChoice::default(), &mut BankWeightForkChoice::default(),
&replay_votes_sender,
); );
frozen_banks.sort_by_key(|bank| bank.slot()); frozen_banks.sort_by_key(|bank| bank.slot());

View File

@@ -8,13 +8,15 @@ use crate::{
cluster_info_vote_listener::{ClusterInfoVoteListener, VerifiedVoteSender, VoteTracker}, cluster_info_vote_listener::{ClusterInfoVoteListener, VerifiedVoteSender, VoteTracker},
fetch_stage::FetchStage, fetch_stage::FetchStage,
poh_recorder::{PohRecorder, WorkingBankEntry}, poh_recorder::{PohRecorder, WorkingBankEntry},
replay_stage::ReplayVotesReceiver,
rpc_subscriptions::RpcSubscriptions, rpc_subscriptions::RpcSubscriptions,
sigverify::TransactionSigVerifier, sigverify::TransactionSigVerifier,
sigverify_stage::SigVerifyStage, sigverify_stage::SigVerifyStage,
}; };
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusSender}; use solana_ledger::{
blockstore::Blockstore,
blockstore_processor::{ReplayVotesReceiver, TransactionStatusSender},
};
use solana_runtime::bank_forks::BankForks; use solana_runtime::bank_forks::BankForks;
use std::{ use std::{
net::UdpSocket, net::UdpSocket,

View File

@@ -10,7 +10,7 @@ use crate::{
cluster_slots::ClusterSlots, cluster_slots::ClusterSlots,
ledger_cleanup_service::LedgerCleanupService, ledger_cleanup_service::LedgerCleanupService,
poh_recorder::PohRecorder, poh_recorder::PohRecorder,
replay_stage::{ReplayStage, ReplayStageConfig, ReplayVotesSender}, replay_stage::{ReplayStage, ReplayStageConfig},
retransmit_stage::RetransmitStage, retransmit_stage::RetransmitStage,
rewards_recorder_service::RewardsRecorderSender, rewards_recorder_service::RewardsRecorderSender,
rpc_subscriptions::RpcSubscriptions, rpc_subscriptions::RpcSubscriptions,
@@ -21,7 +21,7 @@ use crate::{
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use solana_ledger::{ use solana_ledger::{
blockstore::{Blockstore, CompletedSlotsReceiver}, blockstore::{Blockstore, CompletedSlotsReceiver},
blockstore_processor::TransactionStatusSender, blockstore_processor::{ReplayVotesSender, TransactionStatusSender},
leader_schedule_cache::LeaderScheduleCache, leader_schedule_cache::LeaderScheduleCache,
}; };
use solana_runtime::{ use solana_runtime::{

View File

@@ -27,7 +27,7 @@ use solana_ledger::{
bank_forks_utils, bank_forks_utils,
blockstore::{Blockstore, CompletedSlotsReceiver, PurgeType}, blockstore::{Blockstore, CompletedSlotsReceiver, PurgeType},
blockstore_db::BlockstoreRecoveryMode, blockstore_db::BlockstoreRecoveryMode,
blockstore_processor::{self, TransactionStatusSender}, blockstore_processor::{self, ReplayVotesSender, TransactionStatusSender},
create_new_tmp_ledger, create_new_tmp_ledger,
leader_schedule::FixedSchedule, leader_schedule::FixedSchedule,
leader_schedule_cache::LeaderScheduleCache, leader_schedule_cache::LeaderScheduleCache,
@@ -223,6 +223,7 @@ impl Validator {
validator_exit.register_exit(Box::new(move || exit_.store(true, Ordering::Relaxed))); validator_exit.register_exit(Box::new(move || exit_.store(true, Ordering::Relaxed)));
let validator_exit = Arc::new(RwLock::new(Some(validator_exit))); let validator_exit = Arc::new(RwLock::new(Some(validator_exit)));
let (replay_votes_sender, replay_votes_receiver) = unbounded();
let ( let (
genesis_config, genesis_config,
bank_forks, bank_forks,
@@ -237,7 +238,7 @@ impl Validator {
rewards_recorder_sender, rewards_recorder_sender,
rewards_recorder_service, rewards_recorder_service,
}, },
) = new_banks_from_ledger(config, ledger_path, poh_verify, &exit); ) = new_banks_from_ledger(config, ledger_path, poh_verify, &exit, &replay_votes_sender);
let leader_schedule_cache = Arc::new(leader_schedule_cache); let leader_schedule_cache = Arc::new(leader_schedule_cache);
let bank = bank_forks.working_bank(); let bank = bank_forks.working_bank();
@@ -407,7 +408,6 @@ impl Validator {
let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded(); let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded();
let (verified_vote_sender, verified_vote_receiver) = unbounded(); let (verified_vote_sender, verified_vote_receiver) = unbounded();
let (replay_votes_sender, replay_votes_receiver) = unbounded();
let tvu = Tvu::new( let tvu = Tvu::new(
vote_account, vote_account,
authorized_voter_keypairs, authorized_voter_keypairs,
@@ -574,6 +574,7 @@ fn new_banks_from_ledger(
ledger_path: &Path, ledger_path: &Path,
poh_verify: bool, poh_verify: bool,
exit: &Arc<AtomicBool>, exit: &Arc<AtomicBool>,
replay_votes_sender: &ReplayVotesSender,
) -> ( ) -> (
GenesisConfig, GenesisConfig,
BankForks, BankForks,
@@ -635,6 +636,7 @@ fn new_banks_from_ledger(
transaction_history_services transaction_history_services
.transaction_status_sender .transaction_status_sender
.clone(), .clone(),
Some(&replay_votes_sender),
) )
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
error!("Failed to load ledger: {:?}", err); error!("Failed to load ledger: {:?}", err);

View File

@@ -9,23 +9,29 @@ pub struct VoteStakeTracker {
} }
impl VoteStakeTracker { impl VoteStakeTracker {
// Returns true if the stake that has voted has just crosssed the supermajority // Returns tuple (is_confirmed, is_new) where
// `is_confirmed` is true if the stake that has voted has just crosssed the supermajority
// of stake // of stake
// `is_new` is true if the vote has not been seen before
pub fn add_vote_pubkey( pub fn add_vote_pubkey(
&mut self, &mut self,
vote_pubkey: Arc<Pubkey>, vote_pubkey: Arc<Pubkey>,
stake: u64, stake: u64,
total_epoch_stake: u64, total_epoch_stake: u64,
) -> bool { ) -> (bool, bool) {
if !self.voted.contains(&vote_pubkey) { let is_new = !self.voted.contains(&vote_pubkey);
if is_new {
self.voted.insert(vote_pubkey); self.voted.insert(vote_pubkey);
let ratio_before = self.stake as f64 / total_epoch_stake as f64; let ratio_before = self.stake as f64 / total_epoch_stake as f64;
self.stake += stake; self.stake += stake;
let ratio_now = self.stake as f64 / total_epoch_stake as f64; let ratio_now = self.stake as f64 / total_epoch_stake as f64;
ratio_before <= VOTE_THRESHOLD_SIZE && ratio_now > VOTE_THRESHOLD_SIZE (
ratio_before <= VOTE_THRESHOLD_SIZE && ratio_now > VOTE_THRESHOLD_SIZE,
is_new,
)
} else { } else {
false (false, is_new)
} }
} }
@@ -48,21 +54,26 @@ mod test {
let mut vote_stake_tracker = VoteStakeTracker::default(); let mut vote_stake_tracker = VoteStakeTracker::default();
for i in 0..10 { for i in 0..10 {
let pubkey = Arc::new(Pubkey::new_rand()); let pubkey = Arc::new(Pubkey::new_rand());
let res = vote_stake_tracker.add_vote_pubkey(pubkey.clone(), 1, total_epoch_stake); let (is_confirmed, is_new) =
vote_stake_tracker.add_vote_pubkey(pubkey.clone(), 1, total_epoch_stake);
let stake = vote_stake_tracker.stake(); let stake = vote_stake_tracker.stake();
let (is_confirmed2, is_new2) =
vote_stake_tracker.add_vote_pubkey(pubkey.clone(), 1, total_epoch_stake); vote_stake_tracker.add_vote_pubkey(pubkey.clone(), 1, total_epoch_stake);
let stake2 = vote_stake_tracker.stake(); let stake2 = vote_stake_tracker.stake();
// Stake should not change from adding same pubkey twice // Stake should not change from adding same pubkey twice
assert_eq!(stake, stake2); assert_eq!(stake, stake2);
assert!(!is_confirmed2);
assert!(!is_new2);
// at i == 7, the voted stake is 70%, which is the first time crossing // at i == 7, the voted stake is 70%, which is the first time crossing
// the supermajority threshold // the supermajority threshold
if i == 6 { if i == 6 {
assert!(res); assert!(is_confirmed);
} else { } else {
assert!(!res); assert!(!is_confirmed);
} }
assert!(is_new);
} }
} }
} }

View File

@@ -686,6 +686,7 @@ fn load_bank_forks(
snapshot_config.as_ref(), snapshot_config.as_ref(),
process_options, process_options,
None, None,
None,
) )
} }

View File

@@ -2,7 +2,7 @@ use crate::{
blockstore::Blockstore, blockstore::Blockstore,
blockstore_processor::{ blockstore_processor::{
self, BlockstoreProcessorError, BlockstoreProcessorResult, ProcessOptions, self, BlockstoreProcessorError, BlockstoreProcessorResult, ProcessOptions,
TransactionStatusSender, ReplayVotesSender, TransactionStatusSender,
}, },
entry::VerifyRecyclers, entry::VerifyRecyclers,
leader_schedule_cache::LeaderScheduleCache, leader_schedule_cache::LeaderScheduleCache,
@@ -36,6 +36,7 @@ pub fn load(
snapshot_config: Option<&SnapshotConfig>, snapshot_config: Option<&SnapshotConfig>,
process_options: ProcessOptions, process_options: ProcessOptions,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> LoadResult { ) -> LoadResult {
if let Some(snapshot_config) = snapshot_config.as_ref() { if let Some(snapshot_config) = snapshot_config.as_ref() {
info!( info!(
@@ -89,6 +90,7 @@ pub fn load(
&process_options, &process_options,
&VerifyRecyclers::default(), &VerifyRecyclers::default(),
transaction_status_sender, transaction_status_sender,
replay_votes_sender,
), ),
Some(deserialized_snapshot_hash), Some(deserialized_snapshot_hash),
); );

View File

@@ -6,7 +6,7 @@ use crate::{
entry::{create_ticks, Entry, EntrySlice, EntryVerificationStatus, VerifyRecyclers}, entry::{create_ticks, Entry, EntrySlice, EntryVerificationStatus, VerifyRecyclers},
leader_schedule_cache::LeaderScheduleCache, leader_schedule_cache::LeaderScheduleCache,
}; };
use crossbeam_channel::Sender; use crossbeam_channel::{Receiver, Sender};
use itertools::Itertools; use itertools::Itertools;
use log::*; use log::*;
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng};
@@ -29,6 +29,7 @@ use solana_sdk::{
timing::duration_as_ms, timing::duration_as_ms,
transaction::{Result, Transaction, TransactionError}, transaction::{Result, Transaction, TransactionError},
}; };
use solana_vote_program::{vote_state::Vote, vote_transaction};
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashMap, collections::HashMap,
@@ -42,6 +43,10 @@ use thiserror::Error;
pub type BlockstoreProcessorResult = pub type BlockstoreProcessorResult =
result::Result<(BankForks, LeaderScheduleCache), BlockstoreProcessorError>; result::Result<(BankForks, LeaderScheduleCache), BlockstoreProcessorError>;
pub type ReplayedVote = (Pubkey, Vote, Option<Hash>);
pub type ReplayVotesSender = Sender<ReplayedVote>;
pub type ReplayVotesReceiver = Receiver<ReplayedVote>;
thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new() thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new()
.num_threads(get_thread_count()) .num_threads(get_thread_count())
.thread_name(|ix| format!("blockstore_processor_{}", ix)) .thread_name(|ix| format!("blockstore_processor_{}", ix))
@@ -93,6 +98,7 @@ fn execute_batch(
batch: &TransactionBatch, batch: &TransactionBatch,
bank: &Arc<Bank>, bank: &Arc<Bank>,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> Result<()> { ) -> Result<()> {
let ( let (
TransactionResults { TransactionResults {
@@ -106,6 +112,19 @@ fn execute_batch(
transaction_status_sender.is_some(), transaction_status_sender.is_some(),
); );
if let Some(replay_votes_sender) = replay_votes_sender {
for (transaction, (processing_result, _)) in
OrderedIterator::new(batch.transactions(), batch.iteration_order())
.zip(&processing_results)
{
if processing_result.is_ok() {
if let Some(parsed_vote) = vote_transaction::parse_vote_transaction(transaction) {
let _ = replay_votes_sender.send(parsed_vote);
}
}
}
}
if let Some(sender) = transaction_status_sender { if let Some(sender) = transaction_status_sender {
send_transaction_status_batch( send_transaction_status_batch(
bank.clone(), bank.clone(),
@@ -126,6 +145,7 @@ fn execute_batches(
batches: &[TransactionBatch], batches: &[TransactionBatch],
entry_callback: Option<&ProcessCallback>, entry_callback: Option<&ProcessCallback>,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> Result<()> { ) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", batches.len()); inc_new_counter_debug!("bank-par_execute_entries-count", batches.len());
let results: Vec<Result<()>> = PAR_THREAD_POOL.with(|thread_pool| { let results: Vec<Result<()>> = PAR_THREAD_POOL.with(|thread_pool| {
@@ -133,7 +153,7 @@ fn execute_batches(
batches batches
.into_par_iter() .into_par_iter()
.map_with(transaction_status_sender, |sender, batch| { .map_with(transaction_status_sender, |sender, batch| {
let result = execute_batch(batch, bank, sender.clone()); let result = execute_batch(batch, bank, sender.clone(), replay_votes_sender);
if let Some(entry_callback) = entry_callback { if let Some(entry_callback) = entry_callback {
entry_callback(bank); entry_callback(bank);
} }
@@ -156,8 +176,16 @@ pub fn process_entries(
entries: &[Entry], entries: &[Entry],
randomize: bool, randomize: bool,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> Result<()> { ) -> Result<()> {
process_entries_with_callback(bank, entries, randomize, None, transaction_status_sender) process_entries_with_callback(
bank,
entries,
randomize,
None,
transaction_status_sender,
replay_votes_sender,
)
} }
fn process_entries_with_callback( fn process_entries_with_callback(
@@ -166,6 +194,7 @@ fn process_entries_with_callback(
randomize: bool, randomize: bool,
entry_callback: Option<&ProcessCallback>, entry_callback: Option<&ProcessCallback>,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> Result<()> { ) -> Result<()> {
// accumulator for entries that can be processed in parallel // accumulator for entries that can be processed in parallel
let mut batches = vec![]; let mut batches = vec![];
@@ -182,6 +211,7 @@ fn process_entries_with_callback(
&batches, &batches,
entry_callback, entry_callback,
transaction_status_sender.clone(), transaction_status_sender.clone(),
replay_votes_sender,
)?; )?;
batches.clear(); batches.clear();
for hash in &tick_hashes { for hash in &tick_hashes {
@@ -237,12 +267,19 @@ fn process_entries_with_callback(
&batches, &batches,
entry_callback, entry_callback,
transaction_status_sender.clone(), transaction_status_sender.clone(),
replay_votes_sender,
)?; )?;
batches.clear(); batches.clear();
} }
} }
} }
execute_batches(bank, &batches, entry_callback, transaction_status_sender)?; execute_batches(
bank,
&batches,
entry_callback,
transaction_status_sender,
replay_votes_sender,
)?;
for hash in tick_hashes { for hash in tick_hashes {
bank.register_tick(&hash); bank.register_tick(&hash);
} }
@@ -308,7 +345,15 @@ pub fn process_blockstore(
info!("processing ledger for slot 0..."); info!("processing ledger for slot 0...");
let recyclers = VerifyRecyclers::default(); let recyclers = VerifyRecyclers::default();
process_bank_0(&bank0, blockstore, &opts, &recyclers)?; process_bank_0(&bank0, blockstore, &opts, &recyclers)?;
process_blockstore_from_root(genesis_config, blockstore, bank0, &opts, &recyclers, None) process_blockstore_from_root(
genesis_config,
blockstore,
bank0,
&opts,
&recyclers,
None,
None,
)
} }
// Process blockstore from a known root bank // Process blockstore from a known root bank
@@ -319,6 +364,7 @@ pub fn process_blockstore_from_root(
opts: &ProcessOptions, opts: &ProcessOptions,
recyclers: &VerifyRecyclers, recyclers: &VerifyRecyclers,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> BlockstoreProcessorResult { ) -> BlockstoreProcessorResult {
info!("processing ledger from slot {}...", bank.slot()); info!("processing ledger from slot {}...", bank.slot());
let allocated = thread_mem_usage::Allocatedp::default(); let allocated = thread_mem_usage::Allocatedp::default();
@@ -384,6 +430,7 @@ pub fn process_blockstore_from_root(
opts, opts,
recyclers, recyclers,
transaction_status_sender, transaction_status_sender,
replay_votes_sender,
)?; )?;
(initial_forks, leader_schedule_cache) (initial_forks, leader_schedule_cache)
} else { } else {
@@ -473,6 +520,7 @@ fn confirm_full_slot(
recyclers: &VerifyRecyclers, recyclers: &VerifyRecyclers,
progress: &mut ConfirmationProgress, progress: &mut ConfirmationProgress,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> result::Result<(), BlockstoreProcessorError> { ) -> result::Result<(), BlockstoreProcessorError> {
let mut timing = ConfirmationTiming::default(); let mut timing = ConfirmationTiming::default();
let skip_verification = !opts.poh_verify; let skip_verification = !opts.poh_verify;
@@ -483,6 +531,7 @@ fn confirm_full_slot(
progress, progress,
skip_verification, skip_verification,
transaction_status_sender, transaction_status_sender,
replay_votes_sender,
opts.entry_callback.as_ref(), opts.entry_callback.as_ref(),
recyclers, recyclers,
)?; )?;
@@ -543,6 +592,7 @@ pub fn confirm_slot(
progress: &mut ConfirmationProgress, progress: &mut ConfirmationProgress,
skip_verification: bool, skip_verification: bool,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
entry_callback: Option<&ProcessCallback>, entry_callback: Option<&ProcessCallback>,
recyclers: &VerifyRecyclers, recyclers: &VerifyRecyclers,
) -> result::Result<(), BlockstoreProcessorError> { ) -> result::Result<(), BlockstoreProcessorError> {
@@ -610,6 +660,7 @@ pub fn confirm_slot(
true, true,
entry_callback, entry_callback,
transaction_status_sender, transaction_status_sender,
replay_votes_sender,
) )
.map_err(BlockstoreProcessorError::from); .map_err(BlockstoreProcessorError::from);
replay_elapsed.stop(); replay_elapsed.stop();
@@ -646,7 +697,15 @@ fn process_bank_0(
) -> result::Result<(), BlockstoreProcessorError> { ) -> result::Result<(), BlockstoreProcessorError> {
assert_eq!(bank0.slot(), 0); assert_eq!(bank0.slot(), 0);
let mut progress = ConfirmationProgress::new(bank0.last_blockhash()); let mut progress = ConfirmationProgress::new(bank0.last_blockhash());
confirm_full_slot(blockstore, bank0, opts, recyclers, &mut progress, None) confirm_full_slot(
blockstore,
bank0,
opts,
recyclers,
&mut progress,
None,
None,
)
.expect("processing for bank 0 must succeed"); .expect("processing for bank 0 must succeed");
bank0.freeze(); bank0.freeze();
Ok(()) Ok(())
@@ -720,6 +779,7 @@ fn load_frozen_forks(
opts: &ProcessOptions, opts: &ProcessOptions,
recyclers: &VerifyRecyclers, recyclers: &VerifyRecyclers,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> result::Result<Vec<Arc<Bank>>, BlockstoreProcessorError> { ) -> result::Result<Vec<Arc<Bank>>, BlockstoreProcessorError> {
let mut initial_forks = HashMap::new(); let mut initial_forks = HashMap::new();
let mut last_status_report = Instant::now(); let mut last_status_report = Instant::now();
@@ -766,6 +826,7 @@ fn load_frozen_forks(
recyclers, recyclers,
&mut progress, &mut progress,
transaction_status_sender.clone(), transaction_status_sender.clone(),
replay_votes_sender,
) )
.is_err() .is_err()
{ {
@@ -816,10 +877,11 @@ fn process_single_slot(
recyclers: &VerifyRecyclers, recyclers: &VerifyRecyclers,
progress: &mut ConfirmationProgress, progress: &mut ConfirmationProgress,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_votes_sender: Option<&ReplayVotesSender>,
) -> result::Result<(), BlockstoreProcessorError> { ) -> result::Result<(), BlockstoreProcessorError> {
// Mark corrupt slots as dead so validators don't replay this slot and // Mark corrupt slots as dead so validators don't replay this slot and
// see DuplicateSignature errors later in ReplayStage // see DuplicateSignature errors later in ReplayStage
confirm_full_slot(blockstore, bank, opts, recyclers, progress, transaction_status_sender).map_err(|err| { confirm_full_slot(blockstore, bank, opts, recyclers, progress, transaction_status_sender, replay_votes_sender).map_err(|err| {
let slot = bank.slot(); let slot = bank.slot();
warn!("slot {} failed to verify: {}", slot, err); warn!("slot {} failed to verify: {}", slot, err);
if blockstore.is_primary_access() { if blockstore.is_primary_access() {
@@ -909,8 +971,12 @@ pub mod tests {
create_genesis_config, create_genesis_config_with_leader, GenesisConfigInfo, create_genesis_config, create_genesis_config_with_leader, GenesisConfigInfo,
}, },
}; };
use crossbeam_channel::unbounded;
use matches::assert_matches; use matches::assert_matches;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use solana_runtime::genesis_utils::{
create_genesis_config_with_vote_accounts, ValidatorVoteKeypairs,
};
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::{ use solana_sdk::{
epoch_schedule::EpochSchedule, epoch_schedule::EpochSchedule,
@@ -921,7 +987,7 @@ pub mod tests {
system_transaction, system_transaction,
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
}; };
use std::sync::RwLock; use std::{collections::BTreeSet, sync::RwLock};
#[test] #[test]
fn test_process_blockstore_with_missing_hashes() { fn test_process_blockstore_with_missing_hashes() {
@@ -1593,7 +1659,7 @@ pub mod tests {
); );
// Now ensure the TX is accepted despite pointing to the ID of an empty entry. // Now ensure the TX is accepted despite pointing to the ID of an empty entry.
process_entries(&bank, &slot_entries, true, None).unwrap(); process_entries(&bank, &slot_entries, true, None, None).unwrap();
assert_eq!(bank.process_transaction(&tx), Ok(())); assert_eq!(bank.process_transaction(&tx), Ok(()));
} }
@@ -1798,7 +1864,7 @@ pub mod tests {
// ensure bank can process a tick // ensure bank can process a tick
assert_eq!(bank.tick_height(), 0); assert_eq!(bank.tick_height(), 0);
let tick = next_entry(&genesis_config.hash(), 1, vec![]); let tick = next_entry(&genesis_config.hash(), 1, vec![]);
assert_eq!(process_entries(&bank, &[tick], true, None), Ok(())); assert_eq!(process_entries(&bank, &[tick], true, None, None), Ok(()));
assert_eq!(bank.tick_height(), 1); assert_eq!(bank.tick_height(), 1);
} }
@@ -1831,7 +1897,7 @@ pub mod tests {
); );
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1, entry_2], true, None), process_entries(&bank, &[entry_1, entry_2], true, None, None),
Ok(()) Ok(())
); );
assert_eq!(bank.get_balance(&keypair1.pubkey()), 2); assert_eq!(bank.get_balance(&keypair1.pubkey()), 2);
@@ -1891,7 +1957,8 @@ pub mod tests {
&bank, &bank,
&[entry_1_to_mint, entry_2_to_3_mint_to_1], &[entry_1_to_mint, entry_2_to_3_mint_to_1],
false, false,
None None,
None,
), ),
Ok(()) Ok(())
); );
@@ -1963,6 +2030,7 @@ pub mod tests {
&[entry_1_to_mint.clone(), entry_2_to_3_mint_to_1.clone()], &[entry_1_to_mint.clone(), entry_2_to_3_mint_to_1.clone()],
false, false,
None, None,
None,
) )
.is_err()); .is_err());
@@ -2074,6 +2142,7 @@ pub mod tests {
], ],
false, false,
None, None,
None,
) )
.is_err()); .is_err());
@@ -2121,7 +2190,7 @@ pub mod tests {
system_transaction::transfer(&keypair2, &keypair4.pubkey(), 1, bank.last_blockhash()); system_transaction::transfer(&keypair2, &keypair4.pubkey(), 1, bank.last_blockhash());
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1, entry_2], true, None), process_entries(&bank, &[entry_1, entry_2], true, None, None),
Ok(()) Ok(())
); );
assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); assert_eq!(bank.get_balance(&keypair3.pubkey()), 1);
@@ -2181,7 +2250,7 @@ pub mod tests {
next_entry_mut(&mut hash, 0, transactions) next_entry_mut(&mut hash, 0, transactions)
}) })
.collect(); .collect();
assert_eq!(process_entries(&bank, &entries, true, None), Ok(())); assert_eq!(process_entries(&bank, &entries, true, None, None), Ok(()));
} }
#[test] #[test]
@@ -2241,7 +2310,7 @@ pub mod tests {
// Transfer lamports to each other // Transfer lamports to each other
let entry = next_entry(&bank.last_blockhash(), 1, tx_vector); let entry = next_entry(&bank.last_blockhash(), 1, tx_vector);
assert_eq!(process_entries(&bank, &[entry], true, None), Ok(())); assert_eq!(process_entries(&bank, &[entry], true, None, None), Ok(()));
bank.squash(); bank.squash();
// Even number keypair should have balance of 2 * initial_lamports and // Even number keypair should have balance of 2 * initial_lamports and
@@ -2299,7 +2368,7 @@ pub mod tests {
system_transaction::transfer(&keypair1, &keypair4.pubkey(), 1, bank.last_blockhash()); system_transaction::transfer(&keypair1, &keypair4.pubkey(), 1, bank.last_blockhash());
let entry_2 = next_entry(&tick.hash, 1, vec![tx]); let entry_2 = next_entry(&tick.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1, tick, entry_2.clone()], true, None), process_entries(&bank, &[entry_1, tick, entry_2.clone()], true, None, None),
Ok(()) Ok(())
); );
assert_eq!(bank.get_balance(&keypair3.pubkey()), 1); assert_eq!(bank.get_balance(&keypair3.pubkey()), 1);
@@ -2310,7 +2379,7 @@ pub mod tests {
system_transaction::transfer(&keypair2, &keypair3.pubkey(), 1, bank.last_blockhash()); system_transaction::transfer(&keypair2, &keypair3.pubkey(), 1, bank.last_blockhash());
let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]); let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_3], true, None), process_entries(&bank, &[entry_3], true, None, None),
Err(TransactionError::AccountNotFound) Err(TransactionError::AccountNotFound)
); );
} }
@@ -2390,7 +2459,7 @@ pub mod tests {
); );
assert_eq!( assert_eq!(
process_entries(&bank, &[entry_1_to_mint], false, None), process_entries(&bank, &[entry_1_to_mint], false, None, None),
Err(TransactionError::AccountInUse) Err(TransactionError::AccountInUse)
); );
@@ -2450,6 +2519,7 @@ pub mod tests {
&recyclers, &recyclers,
&mut ConfirmationProgress::new(bank0.last_blockhash()), &mut ConfirmationProgress::new(bank0.last_blockhash()),
None, None,
None,
) )
.unwrap(); .unwrap();
bank1.squash(); bank1.squash();
@@ -2462,6 +2532,7 @@ pub mod tests {
&opts, &opts,
&recyclers, &recyclers,
None, None,
None,
) )
.unwrap(); .unwrap();
@@ -2543,7 +2614,7 @@ pub mod tests {
}) })
.collect(); .collect();
info!("paying iteration {}", i); info!("paying iteration {}", i);
process_entries(&bank, &entries, true, None).expect("paying failed"); process_entries(&bank, &entries, true, None, None).expect("paying failed");
let entries: Vec<_> = (0..NUM_TRANSFERS) let entries: Vec<_> = (0..NUM_TRANSFERS)
.step_by(NUM_TRANSFERS_PER_ENTRY) .step_by(NUM_TRANSFERS_PER_ENTRY)
@@ -2566,7 +2637,7 @@ pub mod tests {
.collect(); .collect();
info!("refunding iteration {}", i); info!("refunding iteration {}", i);
process_entries(&bank, &entries, true, None).expect("refunding failed"); process_entries(&bank, &entries, true, None, None).expect("refunding failed");
// advance to next block // advance to next block
process_entries( process_entries(
@@ -2576,6 +2647,7 @@ pub mod tests {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
true, true,
None, None,
None,
) )
.expect("process ticks failed"); .expect("process ticks failed");
@@ -2618,7 +2690,7 @@ pub mod tests {
let entry = next_entry(&new_blockhash, 1, vec![tx]); let entry = next_entry(&new_blockhash, 1, vec![tx]);
entries.push(entry); entries.push(entry);
process_entries_with_callback(&bank0, &entries, true, None, None).unwrap(); process_entries_with_callback(&bank0, &entries, true, None, None, None).unwrap();
assert_eq!(bank0.get_balance(&keypair.pubkey()), 1) assert_eq!(bank0.get_balance(&keypair.pubkey()), 1)
} }
@@ -2699,4 +2771,81 @@ pub mod tests {
assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound); assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound);
assert_eq!(signature, account_not_found_sig); assert_eq!(signature, account_not_found_sig);
} }
#[test]
fn test_replay_vote_sender() {
let validator_keypairs: Vec<_> =
(0..10).map(|_| ValidatorVoteKeypairs::new_rand()).collect();
let GenesisConfigInfo {
genesis_config,
voting_keypair: _,
..
} = create_genesis_config_with_vote_accounts(
1_000_000_000,
&validator_keypairs,
vec![100; validator_keypairs.len()],
);
let bank0 = Arc::new(Bank::new(&genesis_config));
bank0.freeze();
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::new_rand(), 1));
// The new blockhash is going to be the hash of the last tick in the block
let bank_1_blockhash = bank1.last_blockhash();
// Create an transaction that references the new blockhash, should still
// be able to find the blockhash if we process transactions all in the same
// batch
let mut expected_successful_voter_pubkeys = BTreeSet::new();
let vote_txs: Vec<_> = validator_keypairs
.iter()
.enumerate()
.map(|(i, validator_keypairs)| {
if i % 3 == 0 {
// These votes are correct
expected_successful_voter_pubkeys
.insert(validator_keypairs.vote_keypair.pubkey());
vote_transaction::new_vote_transaction(
vec![0],
bank0.hash(),
bank_1_blockhash,
&validator_keypairs.node_keypair,
&validator_keypairs.vote_keypair,
&validator_keypairs.vote_keypair,
None,
)
} else if i % 3 == 1 {
// These have the wrong authorized voter
vote_transaction::new_vote_transaction(
vec![0],
bank0.hash(),
bank_1_blockhash,
&validator_keypairs.node_keypair,
&validator_keypairs.vote_keypair,
&Keypair::new(),
None,
)
} else {
// These have an invalid vote for non-existent bank 2
vote_transaction::new_vote_transaction(
vec![bank1.slot() + 1],
bank0.hash(),
bank_1_blockhash,
&validator_keypairs.node_keypair,
&validator_keypairs.vote_keypair,
&validator_keypairs.vote_keypair,
None,
)
}
})
.collect();
let entry = next_entry(&bank_1_blockhash, 1, vote_txs);
let (replay_votes_sender, replay_votes_receiver) = unbounded();
let _ = process_entries(&bank1, &[entry], true, None, Some(&replay_votes_sender));
let successes: BTreeSet<Pubkey> = replay_votes_receiver
.try_iter()
.map(|(vote_pubkey, _, _)| vote_pubkey)
.collect();
assert_eq!(successes, expected_successful_voter_pubkeys);
}
} }