2020-03-15 20:31:05 -07:00
|
|
|
use crate::{
|
|
|
|
cluster_info::{ClusterInfo, GOSSIP_SLEEP_MILLIS},
|
|
|
|
crds_value::CrdsValueLabel,
|
|
|
|
poh_recorder::PohRecorder,
|
2020-05-22 23:23:17 -07:00
|
|
|
pubkey_references::LockedPubkeyReferences,
|
2020-03-15 20:31:05 -07:00
|
|
|
result::{Error, Result},
|
2020-05-17 14:01:08 -07:00
|
|
|
rpc_subscriptions::RpcSubscriptions,
|
2020-03-15 20:31:05 -07:00
|
|
|
sigverify,
|
|
|
|
verified_vote_packets::VerifiedVotePackets,
|
|
|
|
};
|
2020-03-09 22:03:09 -07:00
|
|
|
use crossbeam_channel::{
|
|
|
|
unbounded, Receiver as CrossbeamReceiver, RecvTimeoutError, Sender as CrossbeamSender,
|
|
|
|
};
|
2020-03-15 20:31:05 -07:00
|
|
|
use itertools::izip;
|
2020-03-09 22:03:09 -07:00
|
|
|
use log::*;
|
2019-05-17 07:00:06 -07:00
|
|
|
use solana_metrics::inc_new_counter_debug;
|
2020-03-17 23:30:23 -07:00
|
|
|
use solana_perf::packet::{self, Packets};
|
2020-05-22 14:53:47 -07:00
|
|
|
use solana_runtime::{
|
|
|
|
bank::Bank,
|
2020-06-17 08:27:03 -07:00
|
|
|
bank_forks::BankForks,
|
2020-06-25 21:06:58 -07:00
|
|
|
commitment::VOTE_THRESHOLD_SIZE,
|
2020-05-22 14:53:47 -07:00
|
|
|
epoch_stakes::{EpochAuthorizedVoters, EpochStakes},
|
|
|
|
};
|
2020-03-09 22:03:09 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
clock::{Epoch, Slot},
|
|
|
|
epoch_schedule::EpochSchedule,
|
|
|
|
program_utils::limited_deserialize,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
transaction::Transaction,
|
|
|
|
};
|
2020-03-26 17:55:17 -07:00
|
|
|
use solana_vote_program::vote_instruction::VoteInstruction;
|
2020-03-15 20:31:05 -07:00
|
|
|
use std::{
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
|
|
|
{Arc, Mutex, RwLock},
|
|
|
|
},
|
|
|
|
thread::{self, sleep, Builder, JoinHandle},
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2019-01-31 15:51:29 -08:00
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
// Map from a vote account to the authorized voter for an epoch
|
2020-03-15 20:31:05 -07:00
|
|
|
pub type VerifiedVotePacketsSender = CrossbeamSender<Vec<(CrdsValueLabel, Packets)>>;
|
|
|
|
pub type VerifiedVotePacketsReceiver = CrossbeamReceiver<Vec<(CrdsValueLabel, Packets)>>;
|
|
|
|
pub type VerifiedVoteTransactionsSender = CrossbeamSender<Vec<Transaction>>;
|
|
|
|
pub type VerifiedVoteTransactionsReceiver = CrossbeamReceiver<Vec<Transaction>>;
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
#[derive(Default)]
|
2020-03-09 22:03:09 -07:00
|
|
|
pub struct SlotVoteTracker {
|
|
|
|
voted: HashSet<Arc<Pubkey>>,
|
|
|
|
updates: Option<Vec<Arc<Pubkey>>>,
|
2020-06-22 20:27:45 -07:00
|
|
|
pub total_stake: u64,
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SlotVoteTracker {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn get_updates(&mut self) -> Option<Vec<Arc<Pubkey>>> {
|
|
|
|
self.updates.take()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
#[derive(Default)]
|
2020-03-09 22:03:09 -07:00
|
|
|
pub struct VoteTracker {
|
|
|
|
// Map from a slot to a set of validators who have voted for that slot
|
2020-06-22 20:27:45 -07:00
|
|
|
pub slot_vote_trackers: RwLock<HashMap<Slot, Arc<RwLock<SlotVoteTracker>>>>,
|
2020-03-09 22:03:09 -07:00
|
|
|
// Don't track votes from people who are not staked, acts as a spam filter
|
2020-03-26 17:55:17 -07:00
|
|
|
epoch_authorized_voters: RwLock<HashMap<Epoch, Arc<EpochAuthorizedVoters>>>,
|
|
|
|
leader_schedule_epoch: RwLock<Epoch>,
|
|
|
|
current_epoch: RwLock<Epoch>,
|
2020-05-22 23:23:17 -07:00
|
|
|
keys: LockedPubkeyReferences,
|
2020-03-09 22:03:09 -07:00
|
|
|
epoch_schedule: EpochSchedule,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VoteTracker {
|
|
|
|
pub fn new(root_bank: &Bank) -> Self {
|
|
|
|
let current_epoch = root_bank.epoch();
|
|
|
|
let vote_tracker = Self {
|
2020-03-26 17:55:17 -07:00
|
|
|
leader_schedule_epoch: RwLock::new(current_epoch),
|
|
|
|
current_epoch: RwLock::new(current_epoch),
|
2020-03-09 22:03:09 -07:00
|
|
|
epoch_schedule: *root_bank.epoch_schedule(),
|
2020-03-26 17:55:17 -07:00
|
|
|
..VoteTracker::default()
|
2020-03-09 22:03:09 -07:00
|
|
|
};
|
2020-03-26 17:55:17 -07:00
|
|
|
vote_tracker.process_new_root_bank(&root_bank);
|
|
|
|
assert_eq!(
|
|
|
|
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
|
|
|
root_bank.get_leader_schedule_epoch(root_bank.slot())
|
|
|
|
);
|
|
|
|
assert_eq!(*vote_tracker.current_epoch.read().unwrap(), current_epoch,);
|
2020-03-09 22:03:09 -07:00
|
|
|
vote_tracker
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_slot_vote_tracker(&self, slot: Slot) -> Option<Arc<RwLock<SlotVoteTracker>>> {
|
|
|
|
self.slot_vote_trackers.read().unwrap().get(&slot).cloned()
|
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
pub fn get_authorized_voter(&self, pubkey: &Pubkey, slot: Slot) -> Option<Pubkey> {
|
2020-03-09 22:03:09 -07:00
|
|
|
let epoch = self.epoch_schedule.get_epoch(slot);
|
|
|
|
self.epoch_authorized_voters
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&epoch)
|
|
|
|
.map(|epoch_authorized_voters| epoch_authorized_voters.get(pubkey))
|
|
|
|
.unwrap_or(None)
|
|
|
|
.cloned()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn vote_contains_authorized_voter(
|
|
|
|
vote_tx: &Transaction,
|
|
|
|
authorized_voter: &Pubkey,
|
|
|
|
) -> bool {
|
|
|
|
let message = &vote_tx.message;
|
|
|
|
for (i, key) in message.account_keys.iter().enumerate() {
|
|
|
|
if message.is_signer(i) && key == authorized_voter {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
pub fn insert_vote(&self, slot: Slot, pubkey: Arc<Pubkey>) {
|
|
|
|
let mut w_slot_vote_trackers = self.slot_vote_trackers.write().unwrap();
|
|
|
|
|
|
|
|
let slot_vote_tracker = w_slot_vote_trackers.entry(slot).or_default();
|
|
|
|
|
|
|
|
let mut w_slot_vote_tracker = slot_vote_tracker.write().unwrap();
|
|
|
|
|
|
|
|
w_slot_vote_tracker.voted.insert(pubkey.clone());
|
|
|
|
if let Some(ref mut updates) = w_slot_vote_tracker.updates {
|
|
|
|
updates.push(pubkey.clone())
|
2020-03-09 22:03:09 -07:00
|
|
|
} else {
|
2020-03-26 17:55:17 -07:00
|
|
|
w_slot_vote_tracker.updates = Some(vec![pubkey.clone()]);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
2020-03-26 17:55:17 -07:00
|
|
|
|
2020-05-22 23:23:17 -07:00
|
|
|
self.keys.get_or_insert(&pubkey);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
fn update_leader_schedule_epoch(&self, root_bank: &Bank) {
|
|
|
|
// Update with any newly calculated epoch state about future epochs
|
|
|
|
let start_leader_schedule_epoch = *self.leader_schedule_epoch.read().unwrap();
|
|
|
|
let mut greatest_leader_schedule_epoch = start_leader_schedule_epoch;
|
|
|
|
for leader_schedule_epoch in
|
|
|
|
start_leader_schedule_epoch..=root_bank.get_leader_schedule_epoch(root_bank.slot())
|
|
|
|
{
|
|
|
|
let exists = self
|
|
|
|
.epoch_authorized_voters
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.contains_key(&leader_schedule_epoch);
|
|
|
|
if !exists {
|
|
|
|
let epoch_authorized_voters = root_bank
|
|
|
|
.epoch_stakes(leader_schedule_epoch)
|
|
|
|
.unwrap()
|
|
|
|
.epoch_authorized_voters()
|
|
|
|
.clone();
|
|
|
|
self.epoch_authorized_voters
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(leader_schedule_epoch, epoch_authorized_voters);
|
|
|
|
greatest_leader_schedule_epoch = leader_schedule_epoch;
|
|
|
|
}
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
if greatest_leader_schedule_epoch != start_leader_schedule_epoch {
|
|
|
|
*self.leader_schedule_epoch.write().unwrap() = greatest_leader_schedule_epoch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_new_root(&self, root_bank: &Bank) {
|
|
|
|
// Purge any outdated slot data
|
|
|
|
let new_root = root_bank.slot();
|
|
|
|
let root_epoch = root_bank.epoch();
|
|
|
|
self.slot_vote_trackers
|
2020-03-09 22:03:09 -07:00
|
|
|
.write()
|
|
|
|
.unwrap()
|
2020-03-26 17:55:17 -07:00
|
|
|
.retain(|slot, _| *slot >= new_root);
|
|
|
|
|
|
|
|
let current_epoch = *self.current_epoch.read().unwrap();
|
|
|
|
if root_epoch != current_epoch {
|
|
|
|
// If root moved to a new epoch, purge outdated state
|
|
|
|
self.epoch_authorized_voters
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.retain(|epoch, _| epoch >= &root_epoch);
|
2020-05-22 23:23:17 -07:00
|
|
|
self.keys.purge();
|
2020-03-26 17:55:17 -07:00
|
|
|
*self.current_epoch.write().unwrap() = root_epoch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_new_root_bank(&self, root_bank: &Bank) {
|
|
|
|
self.update_leader_schedule_epoch(root_bank);
|
|
|
|
self.update_new_root(root_bank);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-31 15:51:29 -08:00
|
|
|
pub struct ClusterInfoVoteListener {
|
|
|
|
thread_hdls: Vec<JoinHandle<()>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ClusterInfoVoteListener {
|
|
|
|
pub fn new(
|
2019-03-04 19:53:50 -08:00
|
|
|
exit: &Arc<AtomicBool>,
|
2020-04-21 12:54:45 -07:00
|
|
|
cluster_info: Arc<ClusterInfo>,
|
2019-11-01 14:23:03 -07:00
|
|
|
sender: CrossbeamSender<Vec<Packets>>,
|
2019-04-09 22:06:32 -07:00
|
|
|
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
2020-03-09 22:03:09 -07:00
|
|
|
vote_tracker: Arc<VoteTracker>,
|
|
|
|
bank_forks: Arc<RwLock<BankForks>>,
|
2020-05-17 14:01:08 -07:00
|
|
|
subscriptions: Arc<RpcSubscriptions>,
|
2019-01-31 15:51:29 -08:00
|
|
|
) -> Self {
|
2020-03-09 22:03:09 -07:00
|
|
|
let exit_ = exit.clone();
|
2020-03-15 20:31:05 -07:00
|
|
|
|
|
|
|
let (verified_vote_packets_sender, verified_vote_packets_receiver) = unbounded();
|
|
|
|
let (verified_vote_transactions_sender, verified_vote_transactions_receiver) = unbounded();
|
2020-03-09 22:03:09 -07:00
|
|
|
let listen_thread = Builder::new()
|
2019-01-31 15:51:29 -08:00
|
|
|
.name("solana-cluster_info_vote_listener".to_string())
|
|
|
|
.spawn(move || {
|
2019-04-09 22:06:32 -07:00
|
|
|
let _ = Self::recv_loop(
|
2020-03-09 22:03:09 -07:00
|
|
|
exit_,
|
2019-04-09 22:06:32 -07:00
|
|
|
&cluster_info,
|
2020-03-15 20:31:05 -07:00
|
|
|
verified_vote_packets_sender,
|
|
|
|
verified_vote_transactions_sender,
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let exit_ = exit.clone();
|
|
|
|
let poh_recorder = poh_recorder.clone();
|
|
|
|
let bank_send_thread = Builder::new()
|
|
|
|
.name("solana-cluster_info_bank_send".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
let _ = Self::bank_send_loop(
|
|
|
|
exit_,
|
|
|
|
verified_vote_packets_receiver,
|
2019-04-09 22:06:32 -07:00
|
|
|
poh_recorder,
|
2020-03-15 20:31:05 -07:00
|
|
|
&sender,
|
2019-04-09 22:06:32 -07:00
|
|
|
);
|
2019-01-31 15:51:29 -08:00
|
|
|
})
|
|
|
|
.unwrap();
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
let exit_ = exit.clone();
|
|
|
|
let send_thread = Builder::new()
|
|
|
|
.name("solana-cluster_info_process_votes".to_string())
|
|
|
|
.spawn(move || {
|
2020-03-15 20:31:05 -07:00
|
|
|
let _ = Self::process_votes_loop(
|
|
|
|
exit_,
|
|
|
|
verified_vote_transactions_receiver,
|
|
|
|
vote_tracker,
|
|
|
|
&bank_forks,
|
2020-05-17 14:01:08 -07:00
|
|
|
subscriptions,
|
2020-03-15 20:31:05 -07:00
|
|
|
);
|
2020-03-09 22:03:09 -07:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
2019-01-31 15:51:29 -08:00
|
|
|
Self {
|
2020-03-15 20:31:05 -07:00
|
|
|
thread_hdls: vec![listen_thread, send_thread, bank_send_thread],
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn join(self) -> thread::Result<()> {
|
|
|
|
for thread_hdl in self.thread_hdls {
|
|
|
|
thread_hdl.join()?;
|
2019-01-31 15:51:29 -08:00
|
|
|
}
|
2020-03-09 22:03:09 -07:00
|
|
|
Ok(())
|
2019-01-31 15:51:29 -08:00
|
|
|
}
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2019-01-31 15:51:29 -08:00
|
|
|
fn recv_loop(
|
2019-03-04 19:53:50 -08:00
|
|
|
exit: Arc<AtomicBool>,
|
2020-04-21 12:54:45 -07:00
|
|
|
cluster_info: &ClusterInfo,
|
2020-03-15 20:31:05 -07:00
|
|
|
verified_vote_packets_sender: VerifiedVotePacketsSender,
|
|
|
|
verified_vote_transactions_sender: VerifiedVoteTransactionsSender,
|
2019-01-31 15:51:29 -08:00
|
|
|
) -> Result<()> {
|
2020-03-15 20:31:05 -07:00
|
|
|
let mut last_ts = 0;
|
2019-01-31 15:51:29 -08:00
|
|
|
loop {
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-04-21 12:54:45 -07:00
|
|
|
let (labels, votes, new_ts) = cluster_info.get_votes(last_ts);
|
2020-03-15 20:31:05 -07:00
|
|
|
inc_new_counter_debug!("cluster_info_vote_listener-recv_count", votes.len());
|
|
|
|
|
|
|
|
last_ts = new_ts;
|
2020-04-23 17:04:09 -07:00
|
|
|
if !votes.is_empty() {
|
2020-05-08 10:00:23 -07:00
|
|
|
let (vote_txs, packets) = Self::verify_votes(votes, labels);
|
2020-03-15 20:31:05 -07:00
|
|
|
verified_vote_transactions_sender.send(vote_txs)?;
|
|
|
|
verified_vote_packets_sender.send(packets)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
sleep(Duration::from_millis(GOSSIP_SLEEP_MILLIS));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-23 17:04:09 -07:00
|
|
|
fn verify_votes(
|
|
|
|
votes: Vec<Transaction>,
|
|
|
|
labels: Vec<CrdsValueLabel>,
|
|
|
|
) -> (Vec<Transaction>, Vec<(CrdsValueLabel, Packets)>) {
|
|
|
|
let msgs = packet::to_packets_chunked(&votes, 1);
|
2020-05-08 10:00:23 -07:00
|
|
|
let r = sigverify::ed25519_verify_cpu(&msgs);
|
2020-04-23 17:04:09 -07:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
r.iter()
|
|
|
|
.map(|packets_results| packets_results.len())
|
|
|
|
.sum::<usize>(),
|
|
|
|
votes.len()
|
|
|
|
);
|
|
|
|
|
|
|
|
let (vote_txs, packets) = izip!(
|
|
|
|
labels.into_iter(),
|
|
|
|
votes.into_iter(),
|
|
|
|
r.iter().flatten(),
|
|
|
|
msgs,
|
|
|
|
)
|
|
|
|
.filter_map(|(label, vote, verify_result, packet)| {
|
|
|
|
if *verify_result != 0 {
|
|
|
|
Some((vote, (label, packet)))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unzip();
|
|
|
|
(vote_txs, packets)
|
|
|
|
}
|
|
|
|
|
2020-03-15 20:31:05 -07:00
|
|
|
fn bank_send_loop(
|
|
|
|
exit: Arc<AtomicBool>,
|
|
|
|
verified_vote_packets_receiver: VerifiedVotePacketsReceiver,
|
|
|
|
poh_recorder: Arc<Mutex<PohRecorder>>,
|
|
|
|
packets_sender: &CrossbeamSender<Vec<Packets>>,
|
|
|
|
) -> Result<()> {
|
|
|
|
let mut verified_vote_packets = VerifiedVotePackets::default();
|
|
|
|
let mut time_since_lock = Instant::now();
|
|
|
|
let mut update_version = 0;
|
|
|
|
loop {
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Err(e) = verified_vote_packets
|
|
|
|
.get_and_process_vote_packets(&verified_vote_packets_receiver, &mut update_version)
|
|
|
|
{
|
|
|
|
match e {
|
|
|
|
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Disconnected) => {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
|
|
|
_ => {
|
|
|
|
error!("thread {:?} error {:?}", thread::current().name(), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if time_since_lock.elapsed().as_millis() > GOSSIP_SLEEP_MILLIS as u128 {
|
|
|
|
let bank = poh_recorder.lock().unwrap().bank();
|
|
|
|
if let Some(bank) = bank {
|
|
|
|
let last_version = bank.last_vote_sync.load(Ordering::Relaxed);
|
|
|
|
let (new_version, msgs) = verified_vote_packets.get_latest_votes(last_version);
|
2020-03-09 22:03:09 -07:00
|
|
|
packets_sender.send(msgs)?;
|
2020-03-15 20:31:05 -07:00
|
|
|
bank.last_vote_sync.compare_and_swap(
|
|
|
|
last_version,
|
|
|
|
new_version,
|
|
|
|
Ordering::Relaxed,
|
|
|
|
);
|
|
|
|
time_since_lock = Instant::now();
|
2019-04-26 17:27:31 -07:00
|
|
|
}
|
2019-04-09 22:06:32 -07:00
|
|
|
}
|
2019-01-31 15:51:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
fn process_votes_loop(
|
|
|
|
exit: Arc<AtomicBool>,
|
2020-03-15 20:31:05 -07:00
|
|
|
vote_txs_receiver: VerifiedVoteTransactionsReceiver,
|
2020-03-09 22:03:09 -07:00
|
|
|
vote_tracker: Arc<VoteTracker>,
|
|
|
|
bank_forks: &RwLock<BankForks>,
|
2020-05-17 14:01:08 -07:00
|
|
|
subscriptions: Arc<RpcSubscriptions>,
|
2020-03-09 22:03:09 -07:00
|
|
|
) -> Result<()> {
|
|
|
|
loop {
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let root_bank = bank_forks.read().unwrap().root_bank().clone();
|
2020-03-26 17:55:17 -07:00
|
|
|
vote_tracker.process_new_root_bank(&root_bank);
|
2020-05-22 14:53:47 -07:00
|
|
|
let epoch_stakes = root_bank.epoch_stakes(root_bank.epoch());
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2020-05-17 14:01:08 -07:00
|
|
|
if let Err(e) = Self::get_and_process_votes(
|
|
|
|
&vote_txs_receiver,
|
|
|
|
&vote_tracker,
|
|
|
|
root_bank.slot(),
|
2020-06-08 17:38:14 -07:00
|
|
|
&subscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
epoch_stakes,
|
2020-05-17 14:01:08 -07:00
|
|
|
) {
|
2020-03-09 22:03:09 -07:00
|
|
|
match e {
|
|
|
|
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Disconnected) => {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Error::CrossbeamRecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
|
|
|
_ => {
|
|
|
|
error!("thread {:?} error {:?}", thread::current().name(), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-17 14:01:08 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
pub fn get_and_process_votes_for_tests(
|
|
|
|
vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
|
2020-06-08 17:38:14 -07:00
|
|
|
vote_tracker: &VoteTracker,
|
2020-05-17 14:01:08 -07:00
|
|
|
last_root: Slot,
|
2020-06-08 17:38:14 -07:00
|
|
|
subscriptions: &RpcSubscriptions,
|
2020-05-17 14:01:08 -07:00
|
|
|
) -> Result<()> {
|
2020-05-22 14:53:47 -07:00
|
|
|
Self::get_and_process_votes(
|
|
|
|
vote_txs_receiver,
|
|
|
|
vote_tracker,
|
|
|
|
last_root,
|
|
|
|
subscriptions,
|
|
|
|
None,
|
|
|
|
)
|
2020-05-17 14:01:08 -07:00
|
|
|
}
|
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
fn get_and_process_votes(
|
2020-03-15 20:31:05 -07:00
|
|
|
vote_txs_receiver: &VerifiedVoteTransactionsReceiver,
|
2020-06-08 17:38:14 -07:00
|
|
|
vote_tracker: &VoteTracker,
|
2020-03-09 22:03:09 -07:00
|
|
|
last_root: Slot,
|
2020-06-08 17:38:14 -07:00
|
|
|
subscriptions: &RpcSubscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
epoch_stakes: Option<&EpochStakes>,
|
2020-03-09 22:03:09 -07:00
|
|
|
) -> Result<()> {
|
|
|
|
let timer = Duration::from_millis(200);
|
|
|
|
let mut vote_txs = vote_txs_receiver.recv_timeout(timer)?;
|
|
|
|
while let Ok(new_txs) = vote_txs_receiver.try_recv() {
|
|
|
|
vote_txs.extend(new_txs);
|
2019-01-31 15:51:29 -08:00
|
|
|
}
|
2020-05-22 14:53:47 -07:00
|
|
|
Self::process_votes(
|
|
|
|
vote_tracker,
|
|
|
|
vote_txs,
|
|
|
|
last_root,
|
|
|
|
subscriptions,
|
|
|
|
epoch_stakes,
|
|
|
|
);
|
2019-01-31 15:51:29 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2020-05-17 14:01:08 -07:00
|
|
|
fn process_votes(
|
|
|
|
vote_tracker: &VoteTracker,
|
|
|
|
vote_txs: Vec<Transaction>,
|
|
|
|
root: Slot,
|
2020-06-08 17:38:14 -07:00
|
|
|
subscriptions: &RpcSubscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
epoch_stakes: Option<&EpochStakes>,
|
2020-05-17 14:01:08 -07:00
|
|
|
) {
|
2020-03-09 22:03:09 -07:00
|
|
|
let mut diff: HashMap<Slot, HashSet<Arc<Pubkey>>> = HashMap::new();
|
|
|
|
{
|
|
|
|
let all_slot_trackers = &vote_tracker.slot_vote_trackers;
|
|
|
|
for tx in vote_txs {
|
|
|
|
if let (Some(vote_pubkey), Some(vote_instruction)) = tx
|
|
|
|
.message
|
|
|
|
.instructions
|
|
|
|
.first()
|
|
|
|
.and_then(|first_instruction| {
|
|
|
|
first_instruction.accounts.first().map(|offset| {
|
|
|
|
(
|
|
|
|
tx.message.account_keys.get(*offset as usize),
|
|
|
|
limited_deserialize(&first_instruction.data).ok(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or((None, None))
|
|
|
|
{
|
|
|
|
let vote = {
|
|
|
|
match vote_instruction {
|
|
|
|
VoteInstruction::Vote(vote) => vote,
|
|
|
|
_ => {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if vote.slots.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-17 14:01:08 -07:00
|
|
|
for &slot in vote.slots.iter() {
|
2020-03-09 22:03:09 -07:00
|
|
|
if slot <= root {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
// Don't insert if we already have marked down this pubkey
|
|
|
|
// voting for this slot
|
|
|
|
let maybe_slot_tracker =
|
|
|
|
all_slot_trackers.read().unwrap().get(&slot).cloned();
|
|
|
|
if let Some(slot_tracker) = maybe_slot_tracker {
|
|
|
|
if slot_tracker.read().unwrap().voted.contains(vote_pubkey) {
|
|
|
|
continue;
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-22 23:23:17 -07:00
|
|
|
let unduplicated_pubkey = vote_tracker.keys.get_or_insert(vote_pubkey);
|
|
|
|
diff.entry(slot).or_default().insert(unduplicated_pubkey);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
2020-05-17 14:01:08 -07:00
|
|
|
|
|
|
|
subscriptions.notify_vote(&vote);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (slot, slot_diff) in diff {
|
|
|
|
let slot_tracker = vote_tracker
|
|
|
|
.slot_vote_trackers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&slot)
|
|
|
|
.cloned();
|
|
|
|
if let Some(slot_tracker) = slot_tracker {
|
|
|
|
let mut w_slot_tracker = slot_tracker.write().unwrap();
|
2020-03-26 17:55:17 -07:00
|
|
|
if w_slot_tracker.updates.is_none() {
|
|
|
|
w_slot_tracker.updates = Some(vec![]);
|
|
|
|
}
|
2020-05-22 14:53:47 -07:00
|
|
|
let mut current_stake = 0;
|
|
|
|
for pubkey in slot_diff {
|
|
|
|
Self::sum_stake(&mut current_stake, epoch_stakes, &pubkey);
|
|
|
|
|
|
|
|
w_slot_tracker.voted.insert(pubkey.clone());
|
|
|
|
w_slot_tracker.updates.as_mut().unwrap().push(pubkey);
|
2020-03-09 22:03:09 -07:00
|
|
|
}
|
2020-05-22 14:53:47 -07:00
|
|
|
Self::notify_for_stake_change(
|
|
|
|
current_stake,
|
|
|
|
w_slot_tracker.total_stake,
|
|
|
|
&subscriptions,
|
|
|
|
epoch_stakes,
|
|
|
|
slot,
|
|
|
|
);
|
|
|
|
w_slot_tracker.total_stake += current_stake;
|
2020-03-09 22:03:09 -07:00
|
|
|
} else {
|
2020-05-22 14:53:47 -07:00
|
|
|
let mut total_stake = 0;
|
|
|
|
let voted: HashSet<_> = slot_diff
|
|
|
|
.into_iter()
|
|
|
|
.map(|pubkey| {
|
|
|
|
Self::sum_stake(&mut total_stake, epoch_stakes, &pubkey);
|
|
|
|
pubkey
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Self::notify_for_stake_change(total_stake, 0, &subscriptions, epoch_stakes, slot);
|
2020-03-09 22:03:09 -07:00
|
|
|
let new_slot_tracker = SlotVoteTracker {
|
|
|
|
voted: voted.clone(),
|
|
|
|
updates: Some(voted.into_iter().collect()),
|
2020-05-22 14:53:47 -07:00
|
|
|
total_stake,
|
2020-03-09 22:03:09 -07:00
|
|
|
};
|
|
|
|
vote_tracker
|
|
|
|
.slot_vote_trackers
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(slot, Arc::new(RwLock::new(new_slot_tracker)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-22 14:53:47 -07:00
|
|
|
|
|
|
|
fn notify_for_stake_change(
|
|
|
|
current_stake: u64,
|
|
|
|
previous_stake: u64,
|
2020-06-08 17:38:14 -07:00
|
|
|
subscriptions: &RpcSubscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
epoch_stakes: Option<&EpochStakes>,
|
|
|
|
slot: Slot,
|
|
|
|
) {
|
|
|
|
if let Some(stakes) = epoch_stakes {
|
|
|
|
let supermajority_stake = (stakes.total_stake() as f64 * VOTE_THRESHOLD_SIZE) as u64;
|
|
|
|
if previous_stake < supermajority_stake
|
|
|
|
&& (previous_stake + current_stake) > supermajority_stake
|
|
|
|
{
|
|
|
|
subscriptions.notify_gossip_subscribers(slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sum_stake(sum: &mut u64, epoch_stakes: Option<&EpochStakes>, pubkey: &Pubkey) {
|
|
|
|
if let Some(stakes) = epoch_stakes {
|
|
|
|
if let Some(vote_account) = stakes.stakes().vote_accounts().get(pubkey) {
|
|
|
|
*sum += vote_account.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-31 15:51:29 -08:00
|
|
|
}
|
2019-05-21 21:45:38 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-03-09 22:03:09 -07:00
|
|
|
use super::*;
|
2020-03-17 23:30:23 -07:00
|
|
|
use solana_perf::packet;
|
2020-03-09 22:03:09 -07:00
|
|
|
use solana_runtime::{
|
|
|
|
bank::Bank,
|
2020-06-25 21:06:58 -07:00
|
|
|
commitment::BlockCommitmentCache,
|
2020-03-09 22:03:09 -07:00
|
|
|
genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs},
|
|
|
|
};
|
2019-05-21 21:45:38 -07:00
|
|
|
use solana_sdk::hash::Hash;
|
2020-04-23 17:04:09 -07:00
|
|
|
use solana_sdk::signature::Signature;
|
2020-02-20 13:28:55 -08:00
|
|
|
use solana_sdk::signature::{Keypair, Signer};
|
2020-03-26 17:55:17 -07:00
|
|
|
use solana_vote_program::vote_transaction;
|
2019-05-21 21:45:38 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_max_vote_tx_fits() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let node_keypair = Keypair::new();
|
|
|
|
let vote_keypair = Keypair::new();
|
2020-05-15 09:35:43 -07:00
|
|
|
let slots: Vec<_> = (0..31).collect();
|
2019-05-31 11:45:17 -07:00
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
let vote_tx = vote_transaction::new_vote_transaction(
|
|
|
|
slots,
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&node_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
);
|
2019-05-21 21:45:38 -07:00
|
|
|
|
|
|
|
use bincode::serialized_size;
|
|
|
|
info!("max vote size {}", serialized_size(&vote_tx).unwrap());
|
|
|
|
|
|
|
|
let msgs = packet::to_packets(&[vote_tx]); // panics if won't fit
|
|
|
|
|
|
|
|
assert_eq!(msgs.len(), 1);
|
|
|
|
}
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn vote_contains_authorized_voter() {
|
|
|
|
let node_keypair = Keypair::new();
|
|
|
|
let vote_keypair = Keypair::new();
|
|
|
|
let authorized_voter = Keypair::new();
|
|
|
|
|
|
|
|
let vote_tx = vote_transaction::new_vote_transaction(
|
|
|
|
vec![0],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&node_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
&authorized_voter,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check that the two signing keys pass the check
|
|
|
|
assert!(VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&node_keypair.pubkey()
|
|
|
|
));
|
|
|
|
|
|
|
|
assert!(VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&authorized_voter.pubkey()
|
|
|
|
));
|
|
|
|
|
|
|
|
// Non signing key shouldn't pass the check
|
|
|
|
assert!(!VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&vote_keypair.pubkey()
|
|
|
|
));
|
|
|
|
|
|
|
|
// Set the authorized voter == vote keypair
|
|
|
|
let vote_tx = vote_transaction::new_vote_transaction(
|
|
|
|
vec![0],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&node_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check that the node_keypair and vote keypair pass the authorized voter check
|
|
|
|
assert!(VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&node_keypair.pubkey()
|
|
|
|
));
|
|
|
|
|
|
|
|
assert!(VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&vote_keypair.pubkey()
|
|
|
|
));
|
|
|
|
|
|
|
|
// The other keypair should not pss the cchecck
|
|
|
|
assert!(!VoteTracker::vote_contains_authorized_voter(
|
|
|
|
&vote_tx,
|
|
|
|
&authorized_voter.pubkey()
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
#[test]
|
|
|
|
fn test_update_new_root() {
|
2020-05-17 14:01:08 -07:00
|
|
|
let (vote_tracker, bank, _, _) = setup();
|
2020-03-26 17:55:17 -07:00
|
|
|
|
|
|
|
// Check outdated slots are purged with new root
|
|
|
|
let new_voter = Arc::new(Pubkey::new_rand());
|
|
|
|
// Make separate copy so the original doesn't count toward
|
|
|
|
// the ref count, which would prevent cleanup
|
|
|
|
let new_voter_ = Arc::new(*new_voter);
|
|
|
|
vote_tracker.insert_vote(bank.slot(), new_voter_);
|
|
|
|
assert!(vote_tracker
|
|
|
|
.slot_vote_trackers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.contains_key(&bank.slot()));
|
|
|
|
let bank1 = Bank::new_from_parent(&bank, &Pubkey::default(), bank.slot() + 1);
|
|
|
|
vote_tracker.process_new_root_bank(&bank1);
|
|
|
|
assert!(!vote_tracker
|
|
|
|
.slot_vote_trackers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.contains_key(&bank.slot()));
|
|
|
|
|
|
|
|
// Check `keys` and `epoch_authorized_voters` are purged when new
|
|
|
|
// root bank moves to the next epoch
|
2020-05-22 23:23:17 -07:00
|
|
|
assert!(vote_tracker.keys.0.read().unwrap().contains(&new_voter));
|
2020-03-26 17:55:17 -07:00
|
|
|
let current_epoch = bank.epoch();
|
|
|
|
let new_epoch_bank = Bank::new_from_parent(
|
|
|
|
&bank,
|
|
|
|
&Pubkey::default(),
|
|
|
|
bank.epoch_schedule()
|
|
|
|
.get_first_slot_in_epoch(current_epoch + 1),
|
|
|
|
);
|
|
|
|
vote_tracker.process_new_root_bank(&new_epoch_bank);
|
2020-05-22 23:23:17 -07:00
|
|
|
assert!(!vote_tracker.keys.0.read().unwrap().contains(&new_voter));
|
2020-03-26 17:55:17 -07:00
|
|
|
assert_eq!(
|
|
|
|
*vote_tracker.current_epoch.read().unwrap(),
|
|
|
|
current_epoch + 1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_new_leader_schedule_epoch() {
|
2020-05-17 14:01:08 -07:00
|
|
|
let (vote_tracker, bank, _, _) = setup();
|
2020-03-26 17:55:17 -07:00
|
|
|
|
|
|
|
// Check outdated slots are purged with new root
|
|
|
|
let leader_schedule_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
|
|
|
let next_leader_schedule_epoch = leader_schedule_epoch + 1;
|
|
|
|
let mut next_leader_schedule_computed = bank.slot();
|
|
|
|
loop {
|
|
|
|
next_leader_schedule_computed += 1;
|
|
|
|
if bank.get_leader_schedule_epoch(next_leader_schedule_computed)
|
|
|
|
== next_leader_schedule_epoch
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
bank.get_leader_schedule_epoch(next_leader_schedule_computed),
|
|
|
|
next_leader_schedule_epoch
|
|
|
|
);
|
|
|
|
let next_leader_schedule_bank =
|
|
|
|
Bank::new_from_parent(&bank, &Pubkey::default(), next_leader_schedule_computed);
|
|
|
|
vote_tracker.update_leader_schedule_epoch(&next_leader_schedule_bank);
|
|
|
|
assert_eq!(
|
|
|
|
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
|
|
|
next_leader_schedule_epoch
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
vote_tracker
|
|
|
|
.epoch_authorized_voters
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&next_leader_schedule_epoch)
|
|
|
|
.unwrap(),
|
|
|
|
next_leader_schedule_bank
|
|
|
|
.epoch_stakes(next_leader_schedule_epoch)
|
|
|
|
.unwrap()
|
|
|
|
.epoch_authorized_voters()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
#[test]
|
|
|
|
fn test_process_votes() {
|
|
|
|
// Create some voters at genesis
|
2020-05-17 14:01:08 -07:00
|
|
|
let (vote_tracker, _, validator_voting_keypairs, subscriptions) = setup();
|
2020-03-09 22:03:09 -07:00
|
|
|
let (votes_sender, votes_receiver) = unbounded();
|
|
|
|
|
|
|
|
let vote_slots = vec![1, 2];
|
|
|
|
validator_voting_keypairs.iter().for_each(|keypairs| {
|
|
|
|
let node_keypair = &keypairs.node_keypair;
|
|
|
|
let vote_keypair = &keypairs.vote_keypair;
|
|
|
|
let vote_tx = vote_transaction::new_vote_transaction(
|
|
|
|
vote_slots.clone(),
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
node_keypair,
|
|
|
|
vote_keypair,
|
|
|
|
vote_keypair,
|
|
|
|
);
|
|
|
|
votes_sender.send(vec![vote_tx]).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check that all the votes were registered for each validator correctly
|
2020-05-17 14:01:08 -07:00
|
|
|
ClusterInfoVoteListener::get_and_process_votes(
|
|
|
|
&votes_receiver,
|
|
|
|
&vote_tracker,
|
|
|
|
0,
|
2020-06-08 17:38:14 -07:00
|
|
|
&subscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
None,
|
2020-05-17 14:01:08 -07:00
|
|
|
)
|
|
|
|
.unwrap();
|
2020-03-09 22:03:09 -07:00
|
|
|
for vote_slot in vote_slots {
|
|
|
|
let slot_vote_tracker = vote_tracker.get_slot_vote_tracker(vote_slot).unwrap();
|
|
|
|
let r_slot_vote_tracker = slot_vote_tracker.read().unwrap();
|
|
|
|
for voting_keypairs in &validator_voting_keypairs {
|
|
|
|
let pubkey = voting_keypairs.vote_keypair.pubkey();
|
|
|
|
assert!(r_slot_vote_tracker.voted.contains(&pubkey));
|
|
|
|
assert!(r_slot_vote_tracker
|
|
|
|
.updates
|
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
|
|
|
.contains(&Arc::new(pubkey)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_process_votes2() {
|
|
|
|
// Create some voters at genesis
|
2020-05-17 14:01:08 -07:00
|
|
|
let (vote_tracker, _, validator_voting_keypairs, subscriptions) = setup();
|
2020-03-09 22:03:09 -07:00
|
|
|
// Send some votes to process
|
|
|
|
let (votes_sender, votes_receiver) = unbounded();
|
|
|
|
|
|
|
|
for (i, keyset) in validator_voting_keypairs.chunks(2).enumerate() {
|
|
|
|
let validator_votes: Vec<_> = keyset
|
|
|
|
.iter()
|
|
|
|
.map(|keypairs| {
|
|
|
|
let node_keypair = &keypairs.node_keypair;
|
|
|
|
let vote_keypair = &keypairs.vote_keypair;
|
2020-05-15 09:35:43 -07:00
|
|
|
vote_transaction::new_vote_transaction(
|
2020-03-09 22:03:09 -07:00
|
|
|
vec![i as u64 + 1],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
node_keypair,
|
|
|
|
vote_keypair,
|
|
|
|
vote_keypair,
|
2020-05-15 09:35:43 -07:00
|
|
|
)
|
2020-03-09 22:03:09 -07:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
votes_sender.send(validator_votes).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that all the votes were registered for each validator correctly
|
2020-05-17 14:01:08 -07:00
|
|
|
ClusterInfoVoteListener::get_and_process_votes(
|
|
|
|
&votes_receiver,
|
|
|
|
&vote_tracker,
|
|
|
|
0,
|
2020-06-08 17:38:14 -07:00
|
|
|
&subscriptions,
|
2020-05-22 14:53:47 -07:00
|
|
|
None,
|
2020-05-17 14:01:08 -07:00
|
|
|
)
|
|
|
|
.unwrap();
|
2020-03-09 22:03:09 -07:00
|
|
|
for (i, keyset) in validator_voting_keypairs.chunks(2).enumerate() {
|
|
|
|
let slot_vote_tracker = vote_tracker.get_slot_vote_tracker(i as u64 + 1).unwrap();
|
|
|
|
let r_slot_vote_tracker = &slot_vote_tracker.read().unwrap();
|
|
|
|
for voting_keypairs in keyset {
|
|
|
|
let pubkey = voting_keypairs.vote_keypair.pubkey();
|
|
|
|
assert!(r_slot_vote_tracker.voted.contains(&pubkey));
|
|
|
|
assert!(r_slot_vote_tracker
|
|
|
|
.updates
|
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
|
|
|
.contains(&Arc::new(pubkey)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_voters_by_epoch() {
|
|
|
|
// Create some voters at genesis
|
2020-05-17 14:01:08 -07:00
|
|
|
let (vote_tracker, bank, validator_voting_keypairs, _) = setup();
|
2020-03-09 22:03:09 -07:00
|
|
|
let last_known_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
|
|
|
let last_known_slot = bank
|
|
|
|
.epoch_schedule()
|
|
|
|
.get_last_slot_in_epoch(last_known_epoch);
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
// Check we can get the authorized voters
|
2020-03-09 22:03:09 -07:00
|
|
|
for keypairs in &validator_voting_keypairs {
|
|
|
|
assert!(vote_tracker
|
|
|
|
.get_authorized_voter(&keypairs.vote_keypair.pubkey(), last_known_slot)
|
|
|
|
.is_some());
|
|
|
|
assert!(vote_tracker
|
|
|
|
.get_authorized_voter(&keypairs.vote_keypair.pubkey(), last_known_slot + 1)
|
|
|
|
.is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the set of relevant voters for the next epoch
|
|
|
|
let new_epoch = last_known_epoch + 1;
|
|
|
|
let first_slot_in_new_epoch = bank.epoch_schedule().get_first_slot_in_epoch(new_epoch);
|
|
|
|
let new_keypairs: Vec<_> = (0..10)
|
|
|
|
.map(|_| ValidatorVoteKeypairs::new(Keypair::new(), Keypair::new(), Keypair::new()))
|
|
|
|
.collect();
|
2020-03-26 17:55:17 -07:00
|
|
|
let new_epoch_authorized_voters: HashMap<_, _> = new_keypairs
|
2020-03-09 22:03:09 -07:00
|
|
|
.iter()
|
|
|
|
.chain(validator_voting_keypairs[0..5].iter())
|
2020-03-26 17:55:17 -07:00
|
|
|
.map(|keypair| (keypair.vote_keypair.pubkey(), keypair.vote_keypair.pubkey()))
|
2020-03-09 22:03:09 -07:00
|
|
|
.collect();
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
vote_tracker
|
|
|
|
.epoch_authorized_voters
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(new_epoch, Arc::new(new_epoch_authorized_voters));
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
// These keypairs made it into the new epoch
|
|
|
|
for keypairs in new_keypairs
|
|
|
|
.iter()
|
|
|
|
.chain(validator_voting_keypairs[0..5].iter())
|
|
|
|
{
|
|
|
|
assert!(vote_tracker
|
|
|
|
.get_authorized_voter(&keypairs.vote_keypair.pubkey(), first_slot_in_new_epoch)
|
|
|
|
.is_some());
|
|
|
|
}
|
|
|
|
|
|
|
|
// These keypairs were not refreshed in new epoch
|
|
|
|
for keypairs in validator_voting_keypairs[5..10].iter() {
|
|
|
|
assert!(vote_tracker
|
|
|
|
.get_authorized_voter(&keypairs.vote_keypair.pubkey(), first_slot_in_new_epoch)
|
|
|
|
.is_none());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vote_tracker_references() {
|
|
|
|
// The number of references that get stored for a pubkey every time
|
|
|
|
// a vote is made. One stored in the SlotVoteTracker.voted, one in
|
|
|
|
// SlotVoteTracker.updates
|
|
|
|
let ref_count_per_vote = 2;
|
|
|
|
|
|
|
|
// Create some voters at genesis
|
|
|
|
let validator_voting_keypairs: Vec<_> = (0..2)
|
|
|
|
.map(|_| ValidatorVoteKeypairs::new(Keypair::new(), Keypair::new(), Keypair::new()))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let GenesisConfigInfo { genesis_config, .. } =
|
|
|
|
genesis_utils::create_genesis_config_with_vote_accounts(
|
|
|
|
10_000,
|
|
|
|
&validator_voting_keypairs,
|
2020-03-26 17:55:17 -07:00
|
|
|
100,
|
2020-03-09 22:03:09 -07:00
|
|
|
);
|
|
|
|
let bank = Bank::new(&genesis_config);
|
2020-05-17 14:01:08 -07:00
|
|
|
let exit = Arc::new(AtomicBool::new(false));
|
2020-06-12 10:04:17 -07:00
|
|
|
let bank_forks = BankForks::new(bank);
|
2020-05-17 14:01:08 -07:00
|
|
|
let bank = bank_forks.get(0).unwrap().clone();
|
|
|
|
let vote_tracker = VoteTracker::new(&bank);
|
|
|
|
let subscriptions = Arc::new(RpcSubscriptions::new(
|
|
|
|
&exit,
|
|
|
|
Arc::new(RwLock::new(bank_forks)),
|
2020-06-25 21:06:58 -07:00
|
|
|
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
2020-05-17 14:01:08 -07:00
|
|
|
));
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
// Send a vote to process, should add a reference to the pubkey for that voter
|
|
|
|
// in the tracker
|
|
|
|
let validator0_keypairs = &validator_voting_keypairs[0];
|
|
|
|
let vote_tx = vec![vote_transaction::new_vote_transaction(
|
|
|
|
// Must vote > root to be processed
|
|
|
|
vec![bank.slot() + 1],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&validator0_keypairs.node_keypair,
|
|
|
|
&validator0_keypairs.vote_keypair,
|
|
|
|
&validator0_keypairs.vote_keypair,
|
|
|
|
)];
|
|
|
|
|
2020-06-08 17:38:14 -07:00
|
|
|
ClusterInfoVoteListener::process_votes(&vote_tracker, vote_tx, 0, &subscriptions, None);
|
2020-03-26 17:55:17 -07:00
|
|
|
let ref_count = Arc::strong_count(
|
2020-03-09 22:03:09 -07:00
|
|
|
&vote_tracker
|
2020-03-26 17:55:17 -07:00
|
|
|
.keys
|
2020-05-22 23:23:17 -07:00
|
|
|
.0
|
2020-03-26 17:55:17 -07:00
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&validator0_keypairs.vote_keypair.pubkey())
|
2020-03-09 22:03:09 -07:00
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
// This pubkey voted for a slot, so ref count is `ref_count_per_vote + 1`,
|
|
|
|
// +1 in `vote_tracker.keys` and +ref_count_per_vote for the one vote
|
|
|
|
let mut current_ref_count = ref_count_per_vote + 1;
|
|
|
|
assert_eq!(ref_count, current_ref_count);
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
// Setup next epoch
|
|
|
|
let old_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
|
|
|
let new_epoch = old_epoch + 1;
|
|
|
|
let new_epoch_vote_accounts: HashMap<_, _> = vec![(
|
|
|
|
validator0_keypairs.vote_keypair.pubkey(),
|
|
|
|
validator0_keypairs.vote_keypair.pubkey(),
|
|
|
|
)]
|
2020-03-09 22:03:09 -07:00
|
|
|
.into_iter()
|
|
|
|
.collect();
|
2020-03-26 17:55:17 -07:00
|
|
|
vote_tracker
|
|
|
|
.epoch_authorized_voters
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(new_epoch, Arc::new(new_epoch_vote_accounts));
|
2020-03-09 22:03:09 -07:00
|
|
|
|
2020-03-26 17:55:17 -07:00
|
|
|
// Test with votes across two epochs
|
|
|
|
let first_slot_in_new_epoch = bank.epoch_schedule().get_first_slot_in_epoch(new_epoch);
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
// Make 2 new votes in two different epochs, ref count should go up
|
|
|
|
// by 2 * ref_count_per_vote
|
|
|
|
let vote_txs: Vec<_> = [bank.slot() + 2, first_slot_in_new_epoch]
|
|
|
|
.iter()
|
|
|
|
.map(|slot| {
|
|
|
|
vote_transaction::new_vote_transaction(
|
|
|
|
// Must vote > root to be processed
|
|
|
|
vec![*slot],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&validator0_keypairs.node_keypair,
|
|
|
|
&validator0_keypairs.vote_keypair,
|
|
|
|
&validator0_keypairs.vote_keypair,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2020-06-08 17:38:14 -07:00
|
|
|
ClusterInfoVoteListener::process_votes(&vote_tracker, vote_txs, 0, &subscriptions, None);
|
2020-03-09 22:03:09 -07:00
|
|
|
|
|
|
|
let ref_count = Arc::strong_count(
|
|
|
|
&vote_tracker
|
2020-03-26 17:55:17 -07:00
|
|
|
.keys
|
2020-05-22 23:23:17 -07:00
|
|
|
.0
|
2020-03-26 17:55:17 -07:00
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&validator0_keypairs.vote_keypair.pubkey())
|
2020-03-09 22:03:09 -07:00
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
current_ref_count += 2 * ref_count_per_vote;
|
|
|
|
assert_eq!(ref_count, current_ref_count);
|
|
|
|
}
|
2020-03-26 17:55:17 -07:00
|
|
|
|
2020-05-17 14:01:08 -07:00
|
|
|
fn setup() -> (
|
|
|
|
Arc<VoteTracker>,
|
|
|
|
Arc<Bank>,
|
|
|
|
Vec<ValidatorVoteKeypairs>,
|
|
|
|
Arc<RpcSubscriptions>,
|
|
|
|
) {
|
2020-03-26 17:55:17 -07:00
|
|
|
let validator_voting_keypairs: Vec<_> = (0..10)
|
|
|
|
.map(|_| ValidatorVoteKeypairs::new(Keypair::new(), Keypair::new(), Keypair::new()))
|
|
|
|
.collect();
|
|
|
|
let GenesisConfigInfo { genesis_config, .. } =
|
|
|
|
genesis_utils::create_genesis_config_with_vote_accounts(
|
|
|
|
10_000,
|
|
|
|
&validator_voting_keypairs,
|
|
|
|
100,
|
|
|
|
);
|
|
|
|
let bank = Bank::new(&genesis_config);
|
|
|
|
let vote_tracker = VoteTracker::new(&bank);
|
2020-05-17 14:01:08 -07:00
|
|
|
let exit = Arc::new(AtomicBool::new(false));
|
2020-06-12 10:04:17 -07:00
|
|
|
let bank_forks = BankForks::new(bank);
|
2020-05-17 14:01:08 -07:00
|
|
|
let bank = bank_forks.get(0).unwrap().clone();
|
|
|
|
let subscriptions = Arc::new(RpcSubscriptions::new(
|
|
|
|
&exit,
|
|
|
|
Arc::new(RwLock::new(bank_forks)),
|
2020-06-25 21:06:58 -07:00
|
|
|
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
2020-05-17 14:01:08 -07:00
|
|
|
));
|
2020-03-26 17:55:17 -07:00
|
|
|
|
|
|
|
// Integrity Checks
|
|
|
|
let current_epoch = bank.epoch();
|
|
|
|
let leader_schedule_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
|
|
|
|
|
|
|
// Check the vote tracker has all the known epoch state on construction
|
|
|
|
for epoch in current_epoch..=leader_schedule_epoch {
|
|
|
|
assert_eq!(
|
|
|
|
vote_tracker
|
|
|
|
.epoch_authorized_voters
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(&epoch)
|
|
|
|
.unwrap(),
|
|
|
|
bank.epoch_stakes(epoch).unwrap().epoch_authorized_voters()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the epoch state is correct
|
|
|
|
assert_eq!(
|
|
|
|
*vote_tracker.leader_schedule_epoch.read().unwrap(),
|
|
|
|
leader_schedule_epoch,
|
|
|
|
);
|
|
|
|
assert_eq!(*vote_tracker.current_epoch.read().unwrap(), current_epoch);
|
|
|
|
(
|
|
|
|
Arc::new(vote_tracker),
|
2020-05-17 14:01:08 -07:00
|
|
|
bank,
|
2020-03-26 17:55:17 -07:00
|
|
|
validator_voting_keypairs,
|
2020-05-17 14:01:08 -07:00
|
|
|
subscriptions,
|
2020-03-26 17:55:17 -07:00
|
|
|
)
|
|
|
|
}
|
2020-04-23 17:04:09 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_votes_empty() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let votes = vec![];
|
|
|
|
let labels = vec![];
|
2020-05-08 10:00:23 -07:00
|
|
|
let (vote_txs, packets) = ClusterInfoVoteListener::verify_votes(votes, labels);
|
2020-04-23 17:04:09 -07:00
|
|
|
assert!(vote_txs.is_empty());
|
|
|
|
assert!(packets.is_empty());
|
|
|
|
}
|
|
|
|
|
2020-05-15 09:35:43 -07:00
|
|
|
fn verify_packets_len(packets: &[(CrdsValueLabel, Packets)], ref_value: usize) {
|
2020-04-23 17:04:09 -07:00
|
|
|
let num_packets: usize = packets.iter().map(|p| p.1.packets.len()).sum();
|
|
|
|
assert_eq!(num_packets, ref_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_vote_tx() -> Transaction {
|
|
|
|
let node_keypair = Keypair::new();
|
|
|
|
let vote_keypair = Keypair::new();
|
|
|
|
let auth_voter_keypair = Keypair::new();
|
2020-05-15 09:35:43 -07:00
|
|
|
vote_transaction::new_vote_transaction(
|
2020-04-23 17:04:09 -07:00
|
|
|
vec![0],
|
|
|
|
Hash::default(),
|
|
|
|
Hash::default(),
|
|
|
|
&node_keypair,
|
|
|
|
&vote_keypair,
|
|
|
|
&auth_voter_keypair,
|
2020-05-15 09:35:43 -07:00
|
|
|
)
|
2020-04-23 17:04:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verify_votes_1_pass() {
|
|
|
|
let vote_tx = test_vote_tx();
|
2020-05-15 09:35:43 -07:00
|
|
|
let votes = vec![vote_tx];
|
2020-04-23 17:04:09 -07:00
|
|
|
let labels = vec![CrdsValueLabel::Vote(0, Pubkey::new_rand())];
|
2020-05-08 10:00:23 -07:00
|
|
|
let (vote_txs, packets) = ClusterInfoVoteListener::verify_votes(votes, labels);
|
2020-04-23 17:04:09 -07:00
|
|
|
assert_eq!(vote_txs.len(), 1);
|
|
|
|
verify_packets_len(&packets, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bad_vote() {
|
|
|
|
let vote_tx = test_vote_tx();
|
|
|
|
let mut bad_vote = vote_tx.clone();
|
|
|
|
bad_vote.signatures[0] = Signature::default();
|
|
|
|
let votes = vec![vote_tx.clone(), bad_vote, vote_tx];
|
|
|
|
let label = CrdsValueLabel::Vote(0, Pubkey::new_rand());
|
2020-05-15 09:35:43 -07:00
|
|
|
let labels: Vec<_> = (0..votes.len()).map(|_| label.clone()).collect();
|
2020-05-08 10:00:23 -07:00
|
|
|
let (vote_txs, packets) = ClusterInfoVoteListener::verify_votes(votes, labels);
|
2020-04-23 17:04:09 -07:00
|
|
|
assert_eq!(vote_txs.len(), 2);
|
|
|
|
verify_packets_len(&packets, 2);
|
|
|
|
}
|
2019-05-21 21:45:38 -07:00
|
|
|
}
|