votes only need slots and the last bank hash (#5499)
churn cleanup reverse test slot hashes test check_slots_are_valid updates only send the minimum bank vote difference fixup! only send the minimum bank vote difference some banks may not have a voting account setup fixup! votes only need slots and the last bank hash fixup! fixup! votes only need slots and the last bank hash fmt fixed compare fixed vote fixup! fixed vote poke ci filter the local votes via the last bank vote
This commit is contained in:
parent
9f354522a7
commit
475f6fe666
|
@ -85,7 +85,6 @@ impl Service for ClusterInfoVoteListener {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::consensus::MAX_RECENT_VOTES;
|
|
||||||
use crate::packet;
|
use crate::packet;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
@ -98,9 +97,8 @@ mod tests {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let node_keypair = Keypair::new();
|
let node_keypair = Keypair::new();
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let votes = (0..MAX_RECENT_VOTES)
|
let slots: Vec<_> = (0..31).into_iter().collect();
|
||||||
.map(|i| Vote::new(i as u64, Hash::default()))
|
let votes = Vote::new(slots, Hash::default());
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), &vote_keypair.pubkey(), votes);
|
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), &vote_keypair.pubkey(), votes);
|
||||||
|
|
||||||
let mut vote_tx = Transaction::new_with_payer(vec![vote_ix], Some(&node_keypair.pubkey()));
|
let mut vote_tx = Transaction::new_with_payer(vec![vote_ix], Some(&node_keypair.pubkey()));
|
||||||
|
|
|
@ -5,12 +5,11 @@ use solana_sdk::account::Account;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY};
|
use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
||||||
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
|
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
|
||||||
pub const MAX_RECENT_VOTES: usize = 16;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct StakeLockout {
|
pub struct StakeLockout {
|
||||||
|
@ -33,7 +32,7 @@ pub struct Tower {
|
||||||
threshold_depth: usize,
|
threshold_depth: usize,
|
||||||
threshold_size: f64,
|
threshold_size: f64,
|
||||||
lockouts: VoteState,
|
lockouts: VoteState,
|
||||||
recent_votes: VecDeque<Vote>,
|
last_vote: Vote,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tower {
|
impl Tower {
|
||||||
|
@ -43,7 +42,7 @@ impl Tower {
|
||||||
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
||||||
threshold_size: VOTE_THRESHOLD_SIZE,
|
threshold_size: VOTE_THRESHOLD_SIZE,
|
||||||
lockouts: VoteState::default(),
|
lockouts: VoteState::default(),
|
||||||
recent_votes: VecDeque::default(),
|
last_vote: Vote::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
tower.initialize_lockouts_from_bank_forks(&bank_forks, vote_account_pubkey);
|
tower.initialize_lockouts_from_bank_forks(&bank_forks, vote_account_pubkey);
|
||||||
|
@ -165,24 +164,52 @@ impl Tower {
|
||||||
.map(|lockout| (lockout.stake as f64 / total_staked as f64) > self.threshold_size)
|
.map(|lockout| (lockout.stake as f64 / total_staked as f64) > self.threshold_size)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
fn new_vote(
|
||||||
pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
|
local_vote_state: &VoteState,
|
||||||
trace!("{} record_vote for {}", self.node_pubkey, slot);
|
slot: u64,
|
||||||
let root_slot = self.lockouts.root_slot;
|
hash: Hash,
|
||||||
let vote = Vote { slot, hash };
|
last_bank_slot: Option<u64>,
|
||||||
self.lockouts.process_vote_unchecked(&vote);
|
) -> Vote {
|
||||||
|
let mut local_vote_state = local_vote_state.clone();
|
||||||
// vote_state doesn't keep around the hashes, so we save them in recent_votes
|
let vote = Vote {
|
||||||
self.recent_votes.push_back(vote);
|
slots: vec![slot],
|
||||||
let slots = self
|
hash,
|
||||||
.lockouts
|
};
|
||||||
|
local_vote_state.process_vote_unchecked(&vote);
|
||||||
|
let slots = if let Some(lbs) = last_bank_slot {
|
||||||
|
local_vote_state
|
||||||
.votes
|
.votes
|
||||||
.iter()
|
.iter()
|
||||||
.skip(self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES))
|
.map(|v| v.slot)
|
||||||
.map(|vote| vote.slot)
|
.skip_while(|s| *s <= lbs)
|
||||||
.collect::<Vec<_>>();
|
.collect()
|
||||||
self.recent_votes
|
} else {
|
||||||
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
|
local_vote_state.votes.iter().map(|v| v.slot).collect()
|
||||||
|
};
|
||||||
|
trace!(
|
||||||
|
"new vote with {:?} {:?} {:?}",
|
||||||
|
last_bank_slot,
|
||||||
|
slots,
|
||||||
|
local_vote_state.votes
|
||||||
|
);
|
||||||
|
Vote { slots, hash }
|
||||||
|
}
|
||||||
|
fn last_bank_vote(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<u64> {
|
||||||
|
let vote_account = bank.vote_accounts().get(vote_account_pubkey)?.1.clone();
|
||||||
|
let bank_vote_state = VoteState::deserialize(&vote_account.data).ok()?;
|
||||||
|
bank_vote_state.votes.iter().map(|v| v.slot).last()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_vote_from_bank(&self, bank: &Bank, vote_account_pubkey: &Pubkey) -> Vote {
|
||||||
|
let last_vote = Self::last_bank_vote(bank, vote_account_pubkey);
|
||||||
|
Self::new_vote(&self.lockouts, bank.slot(), bank.hash(), last_vote)
|
||||||
|
}
|
||||||
|
pub fn record_bank_vote(&mut self, vote: Vote) -> Option<u64> {
|
||||||
|
let slot = *vote.slots.last().unwrap_or(&0);
|
||||||
|
trace!("{} record_vote for {}", self.node_pubkey, slot);
|
||||||
|
let root_slot = self.lockouts.root_slot;
|
||||||
|
self.lockouts.process_vote_unchecked(&vote);
|
||||||
|
self.last_vote = vote;
|
||||||
|
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"tower-vote",
|
"tower-vote",
|
||||||
|
@ -195,9 +222,16 @@ impl Tower {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
|
||||||
|
let vote = Vote {
|
||||||
|
slots: vec![slot],
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
self.record_bank_vote(vote)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recent_votes(&self) -> Vec<Vote> {
|
pub fn last_vote(&self) -> Vote {
|
||||||
self.recent_votes.iter().cloned().collect::<Vec<_>>()
|
self.last_vote.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(&self) -> Option<u64> {
|
pub fn root(&self) -> Option<u64> {
|
||||||
|
@ -766,6 +800,32 @@ mod test {
|
||||||
assert_eq!(stake_lockouts[&2].stake, 1);
|
assert_eq!(stake_lockouts[&2].stake, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_vote() {
|
||||||
|
let local = VoteState::default();
|
||||||
|
let vote = Tower::new_vote(&local, 0, Hash::default(), None);
|
||||||
|
assert_eq!(vote.slots, vec![0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_vote_dup_vote() {
|
||||||
|
let local = VoteState::default();
|
||||||
|
let vote = Tower::new_vote(&local, 0, Hash::default(), Some(0));
|
||||||
|
assert!(vote.slots.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_vote_next_vote() {
|
||||||
|
let mut local = VoteState::default();
|
||||||
|
let vote = Vote {
|
||||||
|
slots: vec![0],
|
||||||
|
hash: Hash::default(),
|
||||||
|
};
|
||||||
|
local.process_vote_unchecked(&vote);
|
||||||
|
let vote = Tower::new_vote(&local, 1, Hash::default(), Some(0));
|
||||||
|
assert_eq!(vote.slots, vec![1]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_forks() {
|
fn test_check_vote_threshold_forks() {
|
||||||
// Create the ancestor relationships
|
// Create the ancestor relationships
|
||||||
|
@ -818,14 +878,16 @@ mod test {
|
||||||
|
|
||||||
fn vote_and_check_recent(num_votes: usize) {
|
fn vote_and_check_recent(num_votes: usize) {
|
||||||
let mut tower = Tower::new_for_tests(1, 0.67);
|
let mut tower = Tower::new_for_tests(1, 0.67);
|
||||||
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
let slots = if num_votes > 0 {
|
||||||
let expected: Vec<_> = (start..num_votes)
|
vec![num_votes as u64 - 1]
|
||||||
.map(|i| Vote::new(i as u64, Hash::default()))
|
} else {
|
||||||
.collect();
|
vec![]
|
||||||
|
};
|
||||||
|
let expected = Vote::new(slots, Hash::default());
|
||||||
for i in 0..num_votes {
|
for i in 0..num_votes {
|
||||||
tower.record_vote(i as u64, Hash::default());
|
tower.record_vote(i as u64, Hash::default());
|
||||||
}
|
}
|
||||||
assert_eq!(expected, tower.recent_votes())
|
assert_eq!(expected, tower.last_vote())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -840,6 +902,6 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recent_votes_exact() {
|
fn test_recent_votes_exact() {
|
||||||
vote_and_check_recent(MAX_RECENT_VOTES)
|
vote_and_check_recent(5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -409,7 +409,8 @@ impl ReplayStage {
|
||||||
T: 'static + KeypairUtil + Send + Sync,
|
T: 'static + KeypairUtil + Send + Sync,
|
||||||
{
|
{
|
||||||
trace!("handle votable bank {}", bank.slot());
|
trace!("handle votable bank {}", bank.slot());
|
||||||
if let Some(new_root) = tower.record_vote(bank.slot(), bank.hash()) {
|
let vote = tower.new_vote_from_bank(bank, vote_account);
|
||||||
|
if let Some(new_root) = tower.record_bank_vote(vote) {
|
||||||
// get the root bank before squash
|
// get the root bank before squash
|
||||||
let root_bank = bank_forks
|
let root_bank = bank_forks
|
||||||
.read()
|
.read()
|
||||||
|
@ -445,11 +446,8 @@ impl ReplayStage {
|
||||||
let node_keypair = cluster_info.read().unwrap().keypair.clone();
|
let node_keypair = cluster_info.read().unwrap().keypair.clone();
|
||||||
|
|
||||||
// Send our last few votes along with the new one
|
// Send our last few votes along with the new one
|
||||||
let vote_ix = vote_instruction::vote(
|
let vote_ix =
|
||||||
&vote_account,
|
vote_instruction::vote(&vote_account, &voting_keypair.pubkey(), tower.last_vote());
|
||||||
&voting_keypair.pubkey(),
|
|
||||||
tower.recent_votes(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut vote_tx =
|
let mut vote_tx =
|
||||||
Transaction::new_with_payer(vec![vote_ix], Some(&node_keypair.pubkey()));
|
Transaction::new_with_payer(vec![vote_ix], Some(&node_keypair.pubkey()));
|
||||||
|
|
|
@ -40,7 +40,7 @@ fn fill_epoch_with_votes(
|
||||||
vec![vote_instruction::vote(
|
vec![vote_instruction::vote(
|
||||||
&vote_pubkey,
|
&vote_pubkey,
|
||||||
&vote_pubkey,
|
&vote_pubkey,
|
||||||
vec![Vote::new(parent.slot() as u64, parent.hash())],
|
Vote::new(vec![parent.slot() as u64], parent.hash()),
|
||||||
)],
|
)],
|
||||||
Some(&mint_pubkey),
|
Some(&mint_pubkey),
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub enum VoteInstruction {
|
||||||
AuthorizeVoter(Pubkey),
|
AuthorizeVoter(Pubkey),
|
||||||
|
|
||||||
/// A Vote instruction with recent votes
|
/// A Vote instruction with recent votes
|
||||||
Vote(Vec<Vote>),
|
Vote(Vote),
|
||||||
|
|
||||||
/// Withdraw some amount of funds
|
/// Withdraw some amount of funds
|
||||||
Withdraw(u64),
|
Withdraw(u64),
|
||||||
|
@ -94,11 +94,7 @@ pub fn authorize_voter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vote(
|
pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) -> Instruction {
|
||||||
vote_pubkey: &Pubkey,
|
|
||||||
authorized_voter_pubkey: &Pubkey,
|
|
||||||
recent_votes: Vec<Vote>,
|
|
||||||
) -> Instruction {
|
|
||||||
let account_metas = metas_for_authorized_signer(
|
let account_metas = metas_for_authorized_signer(
|
||||||
vote_pubkey,
|
vote_pubkey,
|
||||||
authorized_voter_pubkey,
|
authorized_voter_pubkey,
|
||||||
|
@ -110,7 +106,7 @@ pub fn vote(
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas)
|
Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction {
|
pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction {
|
||||||
|
@ -148,19 +144,19 @@ pub fn process_instruction(
|
||||||
VoteInstruction::AuthorizeVoter(voter_pubkey) => {
|
VoteInstruction::AuthorizeVoter(voter_pubkey) => {
|
||||||
vote_state::authorize_voter(me, rest, &voter_pubkey)
|
vote_state::authorize_voter(me, rest, &voter_pubkey)
|
||||||
}
|
}
|
||||||
VoteInstruction::Vote(votes) => {
|
VoteInstruction::Vote(vote) => {
|
||||||
datapoint_warn!("vote-native", ("count", 1, i64));
|
datapoint_warn!("vote-native", ("count", 1, i64));
|
||||||
if rest.len() < 2 {
|
if rest.len() < 2 {
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
Err(InstructionError::InvalidInstructionData)?;
|
||||||
}
|
}
|
||||||
let (slot_hashes_and_clock, other_signers) = rest.split_at_mut(2);
|
let (slot_hashes_and_clock, other_signers) = rest.split_at_mut(2);
|
||||||
|
|
||||||
vote_state::process_votes(
|
vote_state::process_vote(
|
||||||
me,
|
me,
|
||||||
&sysvar::slot_hashes::from_keyed_account(&slot_hashes_and_clock[0])?,
|
&sysvar::slot_hashes::from_keyed_account(&slot_hashes_and_clock[0])?,
|
||||||
&sysvar::clock::from_keyed_account(&slot_hashes_and_clock[1])?,
|
&sysvar::clock::from_keyed_account(&slot_hashes_and_clock[1])?,
|
||||||
other_signers,
|
other_signers,
|
||||||
&votes,
|
&vote,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
VoteInstruction::Withdraw(lamports) => {
|
VoteInstruction::Withdraw(lamports) => {
|
||||||
|
@ -232,7 +228,7 @@ mod tests {
|
||||||
process_instruction(&vote(
|
process_instruction(&vote(
|
||||||
&Pubkey::default(),
|
&Pubkey::default(),
|
||||||
&Pubkey::default(),
|
&Pubkey::default(),
|
||||||
vec![Vote::default()]
|
Vote::default(),
|
||||||
)),
|
)),
|
||||||
Err(InstructionError::InvalidAccountData),
|
Err(InstructionError::InvalidAccountData),
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,17 +21,17 @@ pub const INITIAL_LOCKOUT: usize = 2;
|
||||||
// smaller numbers makes
|
// smaller numbers makes
|
||||||
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
|
pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
|
||||||
|
|
||||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Vote {
|
pub struct Vote {
|
||||||
/// A vote for height slot
|
/// A stack of votes starting with the oldest vote
|
||||||
pub slot: Slot,
|
pub slots: Vec<Slot>,
|
||||||
// signature of the bank's state at given slot
|
/// signature of the bank's state at the last slot
|
||||||
pub hash: Hash,
|
pub hash: Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vote {
|
impl Vote {
|
||||||
pub fn new(slot: Slot, hash: Hash) -> Self {
|
pub fn new(slots: Vec<Slot>, hash: Hash) -> Self {
|
||||||
Self { slot, hash }
|
Self { slots, hash }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ pub struct Lockout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lockout {
|
impl Lockout {
|
||||||
pub fn new(vote: &Vote) -> Self {
|
pub fn new(slot: Slot) -> Self {
|
||||||
Self {
|
Self {
|
||||||
slot: vote.slot,
|
slot,
|
||||||
confirmation_count: 1,
|
confirmation_count: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,52 +147,74 @@ impl VoteState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn check_slots_are_valid(&self, vote: &Vote, slot_hashes: &[(Slot, Hash)]) -> bool {
|
||||||
|
let mut i = 0;
|
||||||
|
let mut j = slot_hashes.len();
|
||||||
|
while i < vote.slots.len() && j > 0 {
|
||||||
|
if self
|
||||||
|
.votes
|
||||||
|
.back()
|
||||||
|
.map_or(false, |old_vote| old_vote.slot >= vote.slots[i])
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if vote.slots[i] != slot_hashes[j - 1].0 {
|
||||||
|
j -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
j -= 1;
|
||||||
|
}
|
||||||
|
if j == slot_hashes.len() {
|
||||||
|
warn!(
|
||||||
|
"{} dropped vote {:?} to old: {:?} ",
|
||||||
|
self.node_pubkey, vote, slot_hashes
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if i != vote.slots.len() {
|
||||||
|
warn!(
|
||||||
|
"{} dropped vote {:?} failed to match slot: {:?}",
|
||||||
|
self.node_pubkey, vote, slot_hashes,
|
||||||
|
);
|
||||||
|
|
||||||
pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(Slot, Hash)], epoch: Epoch) {
|
return false;
|
||||||
votes
|
}
|
||||||
.iter()
|
if slot_hashes[j].1 != vote.hash {
|
||||||
.for_each(|v| self.process_vote(v, slot_hashes, epoch));
|
warn!(
|
||||||
|
"{} dropped vote {:?} failed to match hash {} {}",
|
||||||
|
self.node_pubkey, vote, vote.hash, slot_hashes[j].1
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(Slot, Hash)], epoch: Epoch) {
|
||||||
|
if vote.slots.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !self.check_slots_are_valid(vote, slot_hashes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vote.slots.iter().for_each(|s| self.process_slot(*s, epoch));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(Slot, Hash)], epoch: Epoch) {
|
pub fn process_slot(&mut self, slot: Slot, epoch: Epoch) {
|
||||||
// Ignore votes for slots earlier than we already have votes for
|
// Ignore votes for slots earlier than we already have votes for
|
||||||
if self
|
if self
|
||||||
.votes
|
.votes
|
||||||
.back()
|
.back()
|
||||||
.map_or(false, |old_vote| old_vote.slot >= vote.slot)
|
.map_or(false, |old_vote| old_vote.slot >= slot)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop votes for which there is no matching slot and hash
|
let vote = Lockout::new(slot);
|
||||||
if !slot_hashes
|
|
||||||
.iter()
|
|
||||||
.any(|(slot, hash)| vote.slot == *slot && vote.hash == *hash)
|
|
||||||
{
|
|
||||||
if log_enabled!(log::Level::Warn) {
|
|
||||||
for (slot, hash) in slot_hashes {
|
|
||||||
if vote.slot == *slot {
|
|
||||||
warn!(
|
|
||||||
"{} dropped vote {:?} matched slot {}, but not hash {:?}",
|
|
||||||
self.node_pubkey, vote, *slot, *hash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if vote.hash == *hash {
|
|
||||||
warn!(
|
|
||||||
"{} dropped vote {:?} matched hash {:?}, but not slot {}",
|
|
||||||
self.node_pubkey, vote, *slot, *hash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let vote = Lockout::new(&vote);
|
self.pop_expired_votes(slot);
|
||||||
|
|
||||||
self.pop_expired_votes(vote.slot);
|
// Once the stack is full, pop the oldest lockout and distribute rewards
|
||||||
|
|
||||||
// Once the stack is full, pop the oldest vote and distribute rewards
|
|
||||||
if self.votes.len() == MAX_LOCKOUT_HISTORY {
|
if self.votes.len() == MAX_LOCKOUT_HISTORY {
|
||||||
let vote = self.votes.pop_front().unwrap();
|
let vote = self.votes.pop_front().unwrap();
|
||||||
self.root_slot = Some(vote.slot);
|
self.root_slot = Some(vote.slot);
|
||||||
|
@ -226,10 +248,11 @@ impl VoteState {
|
||||||
|
|
||||||
/// "unchecked" functions used by tests and Tower
|
/// "unchecked" functions used by tests and Tower
|
||||||
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
||||||
self.process_vote(vote, &[(vote.slot, vote.hash)], self.epoch);
|
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
|
||||||
|
self.process_vote(vote, &slot_hashes, self.epoch);
|
||||||
}
|
}
|
||||||
pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
|
pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
|
||||||
self.process_vote_unchecked(&Vote::new(slot, Hash::default()));
|
self.process_vote_unchecked(&Vote::new(vec![slot], Hash::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
|
pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
|
||||||
|
@ -337,12 +360,12 @@ pub fn initialize_account(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_votes(
|
pub fn process_vote(
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
slot_hashes: &[(Slot, Hash)],
|
slot_hashes: &[(Slot, Hash)],
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
other_signers: &[KeyedAccount],
|
other_signers: &[KeyedAccount],
|
||||||
votes: &[Vote],
|
vote: &Vote,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut vote_state: VoteState = vote_account.state()?;
|
let mut vote_state: VoteState = vote_account.state()?;
|
||||||
|
|
||||||
|
@ -360,7 +383,7 @@ pub fn process_votes(
|
||||||
return Err(InstructionError::MissingRequiredSignature);
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_state.process_votes(&votes, slot_hashes, clock.epoch);
|
vote_state.process_vote(vote, slot_hashes, clock.epoch);
|
||||||
vote_account.set_state(&vote_state)
|
vote_account.set_state(&vote_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +445,7 @@ mod tests {
|
||||||
slot_hashes: &[(u64, Hash)],
|
slot_hashes: &[(u64, Hash)],
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
) -> Result<VoteState, InstructionError> {
|
) -> Result<VoteState, InstructionError> {
|
||||||
process_votes(
|
process_vote(
|
||||||
&mut KeyedAccount::new(vote_pubkey, true, vote_account),
|
&mut KeyedAccount::new(vote_pubkey, true, vote_account),
|
||||||
slot_hashes,
|
slot_hashes,
|
||||||
&Clock {
|
&Clock {
|
||||||
|
@ -430,7 +453,7 @@ mod tests {
|
||||||
..Clock::default()
|
..Clock::default()
|
||||||
},
|
},
|
||||||
&[],
|
&[],
|
||||||
&[vote.clone()],
|
&vote.clone(),
|
||||||
)?;
|
)?;
|
||||||
vote_account.state()
|
vote_account.state()
|
||||||
}
|
}
|
||||||
|
@ -445,7 +468,7 @@ mod tests {
|
||||||
vote_pubkey,
|
vote_pubkey,
|
||||||
vote_account,
|
vote_account,
|
||||||
vote,
|
vote,
|
||||||
&[(vote.slot, vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -475,10 +498,13 @@ mod tests {
|
||||||
fn test_vote() {
|
fn test_vote() {
|
||||||
let (vote_pubkey, mut vote_account) = create_test_account();
|
let (vote_pubkey, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote = Vote::new(1, Hash::default());
|
let vote = Vote::new(vec![1], Hash::default());
|
||||||
let vote_state =
|
let vote_state =
|
||||||
simulate_process_vote_unchecked(&vote_pubkey, &mut vote_account, &vote).unwrap();
|
simulate_process_vote_unchecked(&vote_pubkey, &mut vote_account, &vote).unwrap();
|
||||||
assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]);
|
assert_eq!(
|
||||||
|
vote_state.votes,
|
||||||
|
vec![Lockout::new(*vote.slots.last().unwrap())]
|
||||||
|
);
|
||||||
assert_eq!(vote_state.credits(), 0);
|
assert_eq!(vote_state.credits(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,7 +513,7 @@ mod tests {
|
||||||
let (vote_pubkey, mut vote_account) = create_test_account();
|
let (vote_pubkey, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let hash = hash(&[0u8]);
|
let hash = hash(&[0u8]);
|
||||||
let vote = Vote::new(0, hash);
|
let vote = Vote::new(vec![0], hash);
|
||||||
|
|
||||||
// wrong hash
|
// wrong hash
|
||||||
let vote_state = simulate_process_vote(
|
let vote_state = simulate_process_vote(
|
||||||
|
@ -515,25 +541,25 @@ mod tests {
|
||||||
fn test_vote_signature() {
|
fn test_vote_signature() {
|
||||||
let (vote_pubkey, mut vote_account) = create_test_account();
|
let (vote_pubkey, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote = Vote::new(1, Hash::default());
|
let vote = Vote::new(vec![1], Hash::default());
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||||
&[(vote.slot, vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock::default(),
|
||||||
&[],
|
&[],
|
||||||
&[vote],
|
&vote,
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||||
&[(vote.slot, vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock::default(),
|
||||||
&[],
|
&[],
|
||||||
&[vote],
|
&vote,
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
|
@ -565,28 +591,28 @@ mod tests {
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
// not signed by authorized voter
|
// not signed by authorized voter
|
||||||
let vote = Vote::new(2, Hash::default());
|
let vote = Vote::new(vec![2], Hash::default());
|
||||||
let res = process_votes(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account),
|
||||||
&[(vote.slot, vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock::default(),
|
||||||
&[],
|
&[],
|
||||||
&[vote],
|
&vote,
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
|
|
||||||
// signed by authorized voter
|
// signed by authorized voter
|
||||||
let vote = Vote::new(2, Hash::default());
|
let vote = Vote::new(vec![2], Hash::default());
|
||||||
let res = process_votes(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account),
|
||||||
&[(vote.slot, vote.hash)],
|
&[(*vote.slots.last().unwrap(), vote.hash)],
|
||||||
&Clock::default(),
|
&Clock::default(),
|
||||||
&[KeyedAccount::new(
|
&[KeyedAccount::new(
|
||||||
&authorized_voter_pubkey,
|
&authorized_voter_pubkey,
|
||||||
true,
|
true,
|
||||||
&mut Account::default(),
|
&mut Account::default(),
|
||||||
)],
|
)],
|
||||||
&[vote],
|
&vote,
|
||||||
);
|
);
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
}
|
}
|
||||||
|
@ -599,7 +625,7 @@ mod tests {
|
||||||
let res = simulate_process_vote_unchecked(
|
let res = simulate_process_vote_unchecked(
|
||||||
&vote_pubkey,
|
&vote_pubkey,
|
||||||
&mut vote_account,
|
&mut vote_account,
|
||||||
&Vote::new(1, Hash::default()),
|
&Vote::new(vec![1], Hash::default()),
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::UninitializedAccount));
|
assert_eq!(res, Err(InstructionError::UninitializedAccount));
|
||||||
}
|
}
|
||||||
|
@ -649,12 +675,12 @@ mod tests {
|
||||||
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 1) as u64);
|
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 1) as u64);
|
||||||
check_lockouts(&vote_state);
|
check_lockouts(&vote_state);
|
||||||
|
|
||||||
// Vote again, this time the vote stack depth increases, so the lockouts should
|
// Vote again, this time the vote stack depth increases, so the votes should
|
||||||
// double for everybody
|
// double for everybody
|
||||||
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 2) as u64);
|
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 2) as u64);
|
||||||
check_lockouts(&vote_state);
|
check_lockouts(&vote_state);
|
||||||
|
|
||||||
// Vote again, this time the vote stack depth increases, so the lockouts should
|
// Vote again, this time the vote stack depth increases, so the votes should
|
||||||
// double for everybody
|
// double for everybody
|
||||||
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 3) as u64);
|
vote_state.process_slot_vote_unchecked((2 + INITIAL_LOCKOUT + 3) as u64);
|
||||||
check_lockouts(&vote_state);
|
check_lockouts(&vote_state);
|
||||||
|
@ -740,18 +766,15 @@ mod tests {
|
||||||
|
|
||||||
fn check_lockouts(vote_state: &VoteState) {
|
fn check_lockouts(vote_state: &VoteState) {
|
||||||
for (i, vote) in vote_state.votes.iter().enumerate() {
|
for (i, vote) in vote_state.votes.iter().enumerate() {
|
||||||
let num_lockouts = vote_state.votes.len() - i;
|
let num_votes = vote_state.votes.len() - i;
|
||||||
assert_eq!(
|
assert_eq!(vote.lockout(), INITIAL_LOCKOUT.pow(num_votes as u32) as u64);
|
||||||
vote.lockout(),
|
|
||||||
INITIAL_LOCKOUT.pow(num_lockouts as u32) as u64
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recent_votes(vote_state: &VoteState) -> Vec<Vote> {
|
fn recent_votes(vote_state: &VoteState) -> Vec<Vote> {
|
||||||
let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
|
let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
|
||||||
(start..vote_state.votes.len())
|
(start..vote_state.votes.len())
|
||||||
.map(|i| Vote::new(vote_state.votes.get(i).unwrap().slot, Hash::default()))
|
.map(|i| Vote::new(vec![vote_state.votes.get(i).unwrap().slot], Hash::default()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,17 +793,96 @@ mod tests {
|
||||||
assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||||
|
|
||||||
// as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
|
// as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
|
||||||
let votes: Vec<_> = (0..MAX_RECENT_VOTES)
|
let slots = (0u64..MAX_RECENT_VOTES as u64).into_iter().collect();
|
||||||
.into_iter()
|
let vote = Vote::new(slots, Hash::default());
|
||||||
.map(|i| Vote::new(i as u64, Hash::default()))
|
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
|
||||||
.collect();
|
|
||||||
let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect();
|
|
||||||
|
|
||||||
vote_state_a.process_votes(&votes, &slot_hashes, 0);
|
vote_state_a.process_vote(&vote, &slot_hashes, 0);
|
||||||
vote_state_b.process_votes(&votes, &slot_hashes, 0);
|
vote_state_b.process_vote(&vote, &slot_hashes, 0);
|
||||||
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_vote_skips_old_vote() {
|
||||||
|
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap() + 1, vote.hash)];
|
||||||
|
vote_state.process_vote(&vote, &slot_hashes, 0);
|
||||||
|
assert!(recent_votes(&vote_state).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_vote_empty_slot_hashes() {
|
||||||
|
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &vec![]), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_new_vote() {
|
||||||
|
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_bad_hash() {
|
||||||
|
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_bad_slot() {
|
||||||
|
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![1], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_duplicate_vote() {
|
||||||
|
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||||
|
vote_state.process_vote(&vote, &slot_hashes, 0);
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_next_vote() {
|
||||||
|
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||||
|
vote_state.process_vote(&vote, &slot_hashes, 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0, 1], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_slots_are_valid_next_vote_only() {
|
||||||
|
let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![0], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||||
|
vote_state.process_vote(&vote, &slot_hashes, 0);
|
||||||
|
|
||||||
|
let vote = Vote::new(vec![1], Hash::default());
|
||||||
|
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
|
||||||
|
assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_state_commission_split() {
|
fn test_vote_state_commission_split() {
|
||||||
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0);
|
||||||
|
|
Loading…
Reference in New Issue