add bank hash to votes (#4381)
This commit is contained in:
parent
de6838da78
commit
578c2ad3ea
|
@ -82,3 +82,41 @@ impl Service for ClusterInfoVoteListener {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::locktower::MAX_RECENT_VOTES;
|
||||||
|
use crate::packet;
|
||||||
|
use solana_sdk::hash::Hash;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::transaction::Transaction;
|
||||||
|
use solana_vote_api::vote_instruction;
|
||||||
|
use solana_vote_api::vote_state::Vote;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_vote_tx_fits() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let node_keypair = Keypair::new();
|
||||||
|
let vote_keypair = Keypair::new();
|
||||||
|
let votes = (0..MAX_RECENT_VOTES)
|
||||||
|
.map(|i| Vote::new(i as u64, Hash::default()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let vote_ix = vote_instruction::vote(
|
||||||
|
&node_keypair.pubkey(),
|
||||||
|
&vote_keypair.pubkey(),
|
||||||
|
&vote_keypair.pubkey(),
|
||||||
|
votes,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut vote_tx = Transaction::new_unsigned_instructions(vec![vote_ix]);
|
||||||
|
vote_tx.partial_sign(&[&node_keypair], Hash::default());
|
||||||
|
vote_tx.partial_sign(&[&vote_keypair], Hash::default());
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,13 +4,15 @@ use hashbrown::{HashMap, HashSet};
|
||||||
use solana_metrics::datapoint_info;
|
use solana_metrics::datapoint_info;
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
|
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::VecDeque;
|
||||||
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;
|
||||||
const MAX_RECENT_VOTES: usize = 16;
|
pub const MAX_RECENT_VOTES: usize = 16;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EpochStakes {
|
pub struct EpochStakes {
|
||||||
|
@ -33,6 +35,7 @@ pub struct Locktower {
|
||||||
threshold_depth: usize,
|
threshold_depth: usize,
|
||||||
threshold_size: f64,
|
threshold_size: f64,
|
||||||
lockouts: VoteState,
|
lockouts: VoteState,
|
||||||
|
recent_votes: VecDeque<Vote>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpochStakes {
|
impl EpochStakes {
|
||||||
|
@ -83,6 +86,7 @@ impl Locktower {
|
||||||
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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bank = locktower.find_heaviest_bank(bank_forks).unwrap();
|
let bank = locktower.find_heaviest_bank(bank_forks).unwrap();
|
||||||
|
@ -96,6 +100,7 @@ impl Locktower {
|
||||||
threshold_depth,
|
threshold_depth,
|
||||||
threshold_size,
|
threshold_size,
|
||||||
lockouts: VoteState::default(),
|
lockouts: VoteState::default(),
|
||||||
|
recent_votes: VecDeque::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn collect_vote_lockouts<F>(
|
pub fn collect_vote_lockouts<F>(
|
||||||
|
@ -113,8 +118,11 @@ impl Locktower {
|
||||||
if lamports == 0 {
|
if lamports == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut vote_state: VoteState = VoteState::deserialize(&account.data)
|
let vote_state = VoteState::from(&account);
|
||||||
.expect("bank should always have valid VoteState data");
|
if vote_state.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut vote_state = vote_state.unwrap();
|
||||||
|
|
||||||
if key == self.epoch_stakes.delegate_id
|
if key == self.epoch_stakes.delegate_id
|
||||||
|| vote_state.node_id == self.epoch_stakes.delegate_id
|
|| vote_state.node_id == self.epoch_stakes.delegate_id
|
||||||
|
@ -136,7 +144,9 @@ impl Locktower {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let start_root = vote_state.root_slot;
|
let start_root = vote_state.root_slot;
|
||||||
vote_state.process_vote(&Vote { slot: bank_slot });
|
|
||||||
|
vote_state.process_slot_vote_unchecked(bank_slot);
|
||||||
|
|
||||||
for vote in &vote_state.votes {
|
for vote in &vote_state.votes {
|
||||||
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
|
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
|
||||||
}
|
}
|
||||||
|
@ -220,9 +230,23 @@ impl Locktower {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_vote(&mut self, slot: u64) -> Option<u64> {
|
pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
|
||||||
let root_slot = self.lockouts.root_slot;
|
let root_slot = self.lockouts.root_slot;
|
||||||
self.lockouts.process_vote(&Vote { slot });
|
let vote = Vote { slot, hash };
|
||||||
|
self.lockouts.process_vote_unchecked(&vote);
|
||||||
|
|
||||||
|
// vote_state doesn't keep around the hashes, so we save them in recent_votes
|
||||||
|
self.recent_votes.push_back(vote);
|
||||||
|
let slots = self
|
||||||
|
.lockouts
|
||||||
|
.votes
|
||||||
|
.iter()
|
||||||
|
.skip(self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES))
|
||||||
|
.map(|vote| vote.slot)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.recent_votes
|
||||||
|
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
|
||||||
|
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"locktower-vote",
|
"locktower-vote",
|
||||||
("latest", slot, i64),
|
("latest", slot, i64),
|
||||||
|
@ -236,10 +260,7 @@ impl Locktower {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recent_votes(&self) -> Vec<Vote> {
|
pub fn recent_votes(&self) -> Vec<Vote> {
|
||||||
let start = self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES);
|
self.recent_votes.iter().cloned().collect::<Vec<_>>()
|
||||||
(start..self.lockouts.votes.len())
|
|
||||||
.map(|i| Vote::new(self.lockouts.votes[i].slot))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(&self) -> Option<u64> {
|
pub fn root(&self) -> Option<u64> {
|
||||||
|
@ -269,7 +290,7 @@ impl Locktower {
|
||||||
|
|
||||||
pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool {
|
pub fn is_locked_out(&self, slot: u64, descendants: &HashMap<u64, HashSet<u64>>) -> bool {
|
||||||
let mut lockouts = self.lockouts.clone();
|
let mut lockouts = self.lockouts.clone();
|
||||||
lockouts.process_vote(&Vote { slot });
|
lockouts.process_slot_vote_unchecked(slot);
|
||||||
for vote in &lockouts.votes {
|
for vote in &lockouts.votes {
|
||||||
if vote.slot == slot {
|
if vote.slot == slot {
|
||||||
continue;
|
continue;
|
||||||
|
@ -291,7 +312,7 @@ impl Locktower {
|
||||||
stake_lockouts: &HashMap<u64, StakeLockout>,
|
stake_lockouts: &HashMap<u64, StakeLockout>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut lockouts = self.lockouts.clone();
|
let mut lockouts = self.lockouts.clone();
|
||||||
lockouts.process_vote(&Vote { slot });
|
lockouts.process_slot_vote_unchecked(slot);
|
||||||
let vote = lockouts.nth_recent_vote(self.threshold_depth);
|
let vote = lockouts.nth_recent_vote(self.threshold_depth);
|
||||||
if let Some(vote) = vote {
|
if let Some(vote) = vote {
|
||||||
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
||||||
|
@ -386,7 +407,7 @@ mod test {
|
||||||
account.lamports = *lamports;
|
account.lamports = *lamports;
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for slot in *votes {
|
for slot in *votes {
|
||||||
vote_state.process_vote(&Vote { slot: *slot });
|
vote_state.process_slot_vote_unchecked(*slot);
|
||||||
}
|
}
|
||||||
vote_state
|
vote_state
|
||||||
.serialize(&mut account.data)
|
.serialize(&mut account.data)
|
||||||
|
@ -431,7 +452,7 @@ mod test {
|
||||||
let mut locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
let mut locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
||||||
let mut ancestors = HashMap::new();
|
let mut ancestors = HashMap::new();
|
||||||
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
||||||
locktower.record_vote(i as u64);
|
locktower.record_vote(i as u64, Hash::default());
|
||||||
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
|
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
|
||||||
}
|
}
|
||||||
assert_eq!(locktower.lockouts.root_slot, Some(0));
|
assert_eq!(locktower.lockouts.root_slot, Some(0));
|
||||||
|
@ -569,7 +590,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_already_voted() {
|
fn test_check_already_voted() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
assert!(locktower.has_voted(0));
|
assert!(locktower.has_voted(0));
|
||||||
assert!(!locktower.has_voted(1));
|
assert!(!locktower.has_voted(1));
|
||||||
}
|
}
|
||||||
|
@ -580,8 +601,8 @@ mod test {
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
|
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1);
|
locktower.record_vote(1, Hash::default());
|
||||||
assert!(locktower.is_locked_out(0, &descendants));
|
assert!(locktower.is_locked_out(0, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,7 +612,7 @@ mod test {
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.is_locked_out(1, &descendants));
|
assert!(!locktower.is_locked_out(1, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,8 +626,8 @@ mod test {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1);
|
locktower.record_vote(1, Hash::default());
|
||||||
assert!(locktower.is_locked_out(2, &descendants));
|
assert!(locktower.is_locked_out(2, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,10 +637,10 @@ mod test {
|
||||||
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
|
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1);
|
locktower.record_vote(1, Hash::default());
|
||||||
assert!(!locktower.is_locked_out(4, &descendants));
|
assert!(!locktower.is_locked_out(4, &descendants));
|
||||||
locktower.record_vote(4);
|
locktower.record_vote(4, Hash::default());
|
||||||
assert_eq!(locktower.lockouts.votes[0].slot, 0);
|
assert_eq!(locktower.lockouts.votes[0].slot, 0);
|
||||||
assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2);
|
assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2);
|
||||||
assert_eq!(locktower.lockouts.votes[1].slot, 4);
|
assert_eq!(locktower.lockouts.votes[1].slot, 4);
|
||||||
|
@ -638,7 +659,7 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -653,7 +674,7 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
assert!(locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(locktower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,9 +690,9 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1);
|
locktower.record_vote(1, Hash::default());
|
||||||
locktower.record_vote(2);
|
locktower.record_vote(2, Hash::default());
|
||||||
assert!(locktower.check_vote_stake_threshold(6, &stakes));
|
assert!(locktower.check_vote_stake_threshold(6, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +700,7 @@ mod test {
|
||||||
fn test_check_vote_threshold_above_threshold_no_stake() {
|
fn test_check_vote_threshold_above_threshold_no_stake() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = HashMap::new();
|
let stakes = HashMap::new();
|
||||||
locktower.record_vote(0);
|
locktower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,7 +791,7 @@ mod test {
|
||||||
// threshold check
|
// threshold check
|
||||||
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
|
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
|
||||||
for vote in &locktower_votes {
|
for vote in &locktower_votes {
|
||||||
locktower.record_vote(*vote);
|
locktower.record_vote(*vote, Hash::default());
|
||||||
}
|
}
|
||||||
let stakes_lockouts = locktower.collect_vote_lockouts(
|
let stakes_lockouts = locktower.collect_vote_lockouts(
|
||||||
vote_to_evaluate,
|
vote_to_evaluate,
|
||||||
|
@ -791,9 +812,11 @@ mod test {
|
||||||
fn vote_and_check_recent(num_votes: usize) {
|
fn vote_and_check_recent(num_votes: usize) {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
||||||
let expected: Vec<_> = (start..num_votes).map(|i| Vote::new(i as u64)).collect();
|
let expected: Vec<_> = (start..num_votes)
|
||||||
|
.map(|i| Vote::new(i as u64, Hash::default()))
|
||||||
|
.collect();
|
||||||
for i in 0..num_votes {
|
for i in 0..num_votes {
|
||||||
locktower.record_vote(i as u64);
|
locktower.record_vote(i as u64, Hash::default());
|
||||||
}
|
}
|
||||||
assert_eq!(expected, locktower.recent_votes())
|
assert_eq!(expected, locktower.recent_votes())
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,7 +302,7 @@ impl ReplayStage {
|
||||||
where
|
where
|
||||||
T: 'static + KeypairUtil + Send + Sync,
|
T: 'static + KeypairUtil + Send + Sync,
|
||||||
{
|
{
|
||||||
if let Some(new_root) = locktower.record_vote(bank.slot()) {
|
if let Some(new_root) = locktower.record_vote(bank.slot(), bank.hash()) {
|
||||||
// get the root bank before squash
|
// get the root bank before squash
|
||||||
let root_bank = bank_forks
|
let root_bank = bank_forks
|
||||||
.read()
|
.read()
|
||||||
|
|
|
@ -206,14 +206,14 @@ mod tests {
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_vote_api::vote_state::{self, Vote};
|
use solana_vote_api::vote_state;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_delegate_stake() {
|
fn test_stake_delegate_stake() {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
vote_state.process_vote(&Vote::new(i));
|
vote_state.process_slot_vote_unchecked(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vote_pubkey = vote_keypair.pubkey();
|
let vote_pubkey = vote_keypair.pubkey();
|
||||||
|
@ -272,7 +272,7 @@ mod tests {
|
||||||
|
|
||||||
// put a credit in the vote_state
|
// put a credit in the vote_state
|
||||||
while vote_state.credits() == 0 {
|
while vote_state.credits() == 0 {
|
||||||
vote_state.process_vote(&Vote::new(vote_i));
|
vote_state.process_slot_vote_unchecked(vote_i);
|
||||||
vote_i += 1;
|
vote_i += 1;
|
||||||
}
|
}
|
||||||
// this guy can't collect now, not enough stake to get paid on 1 credit
|
// this guy can't collect now, not enough stake to get paid on 1 credit
|
||||||
|
@ -291,7 +291,7 @@ mod tests {
|
||||||
|
|
||||||
// put more credit in the vote_state
|
// put more credit in the vote_state
|
||||||
while vote_state.credits() < 10 {
|
while vote_state.credits() < 10 {
|
||||||
vote_state.process_vote(&Vote::new(vote_i));
|
vote_state.process_slot_vote_unchecked(vote_i);
|
||||||
vote_i += 1;
|
vote_i += 1;
|
||||||
}
|
}
|
||||||
vote_state.commission = 0;
|
vote_state.commission = 0;
|
||||||
|
@ -318,7 +318,7 @@ mod tests {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
vote_state.process_vote(&Vote::new(i));
|
vote_state.process_slot_vote_unchecked(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vote_pubkey = vote_keypair.pubkey();
|
let vote_pubkey = vote_keypair.pubkey();
|
||||||
|
@ -364,7 +364,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// move the vote account forward
|
// move the vote account forward
|
||||||
vote_state.process_vote(&Vote::new(1000));
|
vote_state.process_slot_vote_unchecked(1000);
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
|
|
||||||
// now, no lamports in the pool!
|
// now, no lamports in the pool!
|
||||||
|
@ -392,7 +392,7 @@ mod tests {
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
vote_state.process_vote(&Vote::new(i));
|
vote_state.process_slot_vote_unchecked(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vote_pubkey = vote_keypair.pubkey();
|
let vote_pubkey = vote_keypair.pubkey();
|
||||||
|
@ -421,7 +421,7 @@ mod tests {
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
// go back in time, previous state had 1000 votes
|
// go back in time, previous state had 1000 votes
|
||||||
vote_state.process_vote(&Vote::new(i));
|
vote_state.process_slot_vote_unchecked(i);
|
||||||
}
|
}
|
||||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||||
// voter credits lower than stake_delegate credits... TODO: is this an error?
|
// voter credits lower than stake_delegate credits... TODO: is this an error?
|
||||||
|
|
|
@ -127,7 +127,29 @@ pub fn process_instruction(
|
||||||
}
|
}
|
||||||
VoteInstruction::Vote(votes) => {
|
VoteInstruction::Vote(votes) => {
|
||||||
datapoint_warn!("vote-native", ("count", 1, i64));
|
datapoint_warn!("vote-native", ("count", 1, i64));
|
||||||
vote_state::process_votes(me, other_signers, &votes)
|
// TODO: remove me when Bank does this
|
||||||
|
let valid_votes = votes
|
||||||
|
.iter()
|
||||||
|
.map(|vote| (vote.slot, vote.hash))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
use bincode::serialized_size;
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::account_utils::State;
|
||||||
|
use solana_sdk::syscall::slot_hashes;
|
||||||
|
let mut valid_votes_account = Account::new(
|
||||||
|
0,
|
||||||
|
serialized_size(&valid_votes).unwrap() as usize,
|
||||||
|
&Pubkey::default(),
|
||||||
|
);
|
||||||
|
valid_votes_account.set_state(&valid_votes).unwrap();
|
||||||
|
// END TODO: remove me when Bank does this
|
||||||
|
vote_state::process_votes(
|
||||||
|
me,
|
||||||
|
&mut KeyedAccount::new(&slot_hashes::id(), false, &mut valid_votes_account),
|
||||||
|
other_signers,
|
||||||
|
&votes,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
//! Receive and processes votes from validators
|
//! Receive and processes votes from validators
|
||||||
use crate::id;
|
use crate::id;
|
||||||
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
use bincode::{deserialize, serialize_into, serialized_size, ErrorKind};
|
||||||
|
use log::*;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use solana_sdk::account::{Account, KeyedAccount};
|
use solana_sdk::account::{Account, KeyedAccount};
|
||||||
use solana_sdk::account_utils::State;
|
use solana_sdk::account_utils::State;
|
||||||
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::syscall::slot_hashes;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
// Maximum number of votes to keep around
|
// Maximum number of votes to keep around
|
||||||
|
@ -15,14 +18,15 @@ pub const INITIAL_LOCKOUT: usize = 2;
|
||||||
|
|
||||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Vote {
|
pub struct Vote {
|
||||||
// TODO: add signature of the state here as well
|
|
||||||
/// A vote for height slot
|
/// A vote for height slot
|
||||||
pub slot: u64,
|
pub slot: u64,
|
||||||
|
// signature of the bank's state at given slot
|
||||||
|
pub hash: Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vote {
|
impl Vote {
|
||||||
pub fn new(slot: u64) -> Self {
|
pub fn new(slot: u64, hash: Hash) -> Self {
|
||||||
Self { slot }
|
Self { slot, hash }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,11 +126,11 @@ impl VoteState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_votes(&mut self, votes: &[Vote]) {
|
pub fn process_votes(&mut self, votes: &[Vote], slot_hashes: &[(u64, Hash)]) {
|
||||||
votes.iter().for_each(|v| self.process_vote(v));;
|
votes.iter().for_each(|v| self.process_vote(v, slot_hashes));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_vote(&mut self, vote: &Vote) {
|
pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(u64, Hash)]) {
|
||||||
// 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
|
||||||
|
@ -136,6 +140,18 @@ impl VoteState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drop votes for which there is no matching slot and hash
|
||||||
|
if !slot_hashes
|
||||||
|
.iter()
|
||||||
|
.any(|(slot, hash)| vote.slot == *slot && vote.hash == *hash)
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"dropping vote {:?}, no matching slot/hash combination",
|
||||||
|
vote
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let vote = Lockout::new(&vote);
|
let vote = Lockout::new(&vote);
|
||||||
|
|
||||||
// TODO: Integrity checks
|
// TODO: Integrity checks
|
||||||
|
@ -152,6 +168,13 @@ impl VoteState {
|
||||||
self.double_lockouts();
|
self.double_lockouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
||||||
|
self.process_vote(vote, &[(vote.slot, vote.hash)]);
|
||||||
|
}
|
||||||
|
pub fn process_slot_vote_unchecked(&mut self, slot: u64) {
|
||||||
|
self.process_vote_unchecked(&Vote::new(slot, Hash::default()));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
|
pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
|
||||||
if position < self.votes.len() {
|
if position < self.votes.len() {
|
||||||
let pos = self.votes.len() - 1 - position;
|
let pos = self.votes.len() - 1 - position;
|
||||||
|
@ -235,6 +258,7 @@ pub fn initialize_account(
|
||||||
|
|
||||||
pub fn process_votes(
|
pub fn process_votes(
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
|
slot_hashes_account: &mut KeyedAccount,
|
||||||
other_signers: &[KeyedAccount],
|
other_signers: &[KeyedAccount],
|
||||||
votes: &[Vote],
|
votes: &[Vote],
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
|
@ -244,6 +268,12 @@ pub fn process_votes(
|
||||||
return Err(InstructionError::UninitializedAccount);
|
return Err(InstructionError::UninitializedAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !slot_hashes::check_id(slot_hashes_account.unsigned_key()) {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot_hashes: Vec<(u64, Hash)> = slot_hashes_account.state()?;
|
||||||
|
|
||||||
let authorized = Some(&vote_state.authorized_voter_id);
|
let authorized = Some(&vote_state.authorized_voter_id);
|
||||||
// find a signer that matches the authorized_voter_id
|
// find a signer that matches the authorized_voter_id
|
||||||
if vote_account.signer_key() != authorized
|
if vote_account.signer_key() != authorized
|
||||||
|
@ -254,7 +284,7 @@ pub fn process_votes(
|
||||||
return Err(InstructionError::MissingRequiredSignature);
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_state.process_votes(&votes);
|
vote_state.process_votes(&votes, &slot_hashes);
|
||||||
vote_account.set_state(&vote_state)
|
vote_account.set_state(&vote_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,30 +317,24 @@ pub fn create_bootstrap_leader_account(
|
||||||
// will be forced to select it as the leader for height 0
|
// will be forced to select it as the leader for height 0
|
||||||
let mut vote_account = create_account(&vote_id, &node_id, commission, lamports);
|
let mut vote_account = create_account(&vote_id, &node_id, commission, lamports);
|
||||||
|
|
||||||
vote(&vote_id, &mut vote_account, &Vote::new(0)).unwrap();
|
let mut vote_state: VoteState = vote_account.state().unwrap();
|
||||||
|
// TODO: get a hash for slot 0?
|
||||||
|
vote_state.process_slot_vote_unchecked(0);
|
||||||
|
|
||||||
let vote_state = vote_account.state().expect("account.state()");
|
vote_account.set_state(&vote_state).unwrap();
|
||||||
(vote_account, vote_state)
|
(vote_account, vote_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility function, used by Bank, tests
|
|
||||||
pub fn vote(
|
|
||||||
vote_id: &Pubkey,
|
|
||||||
vote_account: &mut Account,
|
|
||||||
vote: &Vote,
|
|
||||||
) -> Result<VoteState, InstructionError> {
|
|
||||||
process_votes(
|
|
||||||
&mut KeyedAccount::new(vote_id, true, vote_account),
|
|
||||||
&[],
|
|
||||||
&[vote.clone()],
|
|
||||||
)?;
|
|
||||||
vote_account.state()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vote_state;
|
use crate::vote_state;
|
||||||
|
use bincode::serialized_size;
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::account_utils::State;
|
||||||
|
use solana_sdk::hash::hash;
|
||||||
|
use solana_sdk::syscall;
|
||||||
|
use solana_sdk::syscall::slot_hashes;
|
||||||
|
|
||||||
const MAX_RECENT_VOTES: usize = 16;
|
const MAX_RECENT_VOTES: usize = 16;
|
||||||
|
|
||||||
|
@ -339,6 +363,45 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_test_slot_hashes_account(slot_hashes: &[(u64, Hash)]) -> (Pubkey, Account) {
|
||||||
|
let mut slot_hashes_account = Account::new(
|
||||||
|
0,
|
||||||
|
serialized_size(&slot_hashes).unwrap() as usize,
|
||||||
|
&syscall::id(),
|
||||||
|
);
|
||||||
|
slot_hashes_account
|
||||||
|
.set_state(&slot_hashes.to_vec())
|
||||||
|
.unwrap();
|
||||||
|
(slot_hashes::id(), slot_hashes_account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simulate_process_vote(
|
||||||
|
vote_id: &Pubkey,
|
||||||
|
vote_account: &mut Account,
|
||||||
|
vote: &Vote,
|
||||||
|
slot_hashes: &[(u64, Hash)],
|
||||||
|
) -> Result<VoteState, InstructionError> {
|
||||||
|
let (slot_hashes_id, mut slot_hashes_account) =
|
||||||
|
create_test_slot_hashes_account(slot_hashes);
|
||||||
|
|
||||||
|
process_votes(
|
||||||
|
&mut KeyedAccount::new(vote_id, true, vote_account),
|
||||||
|
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
||||||
|
&[],
|
||||||
|
&[vote.clone()],
|
||||||
|
)?;
|
||||||
|
vote_account.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exercises all the keyed accounts stuff
|
||||||
|
fn simulate_process_vote_unchecked(
|
||||||
|
vote_id: &Pubkey,
|
||||||
|
vote_account: &mut Account,
|
||||||
|
vote: &Vote,
|
||||||
|
) -> Result<VoteState, InstructionError> {
|
||||||
|
simulate_process_vote(vote_id, vote_account, vote, &[(vote.slot, vote.hash)])
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_create_bootstrap_leader_account() {
|
fn test_vote_create_bootstrap_leader_account() {
|
||||||
let vote_id = Pubkey::new_rand();
|
let vote_id = Pubkey::new_rand();
|
||||||
|
@ -346,7 +409,7 @@ mod tests {
|
||||||
vote_state::create_bootstrap_leader_account(&vote_id, &Pubkey::new_rand(), 0, 100);
|
vote_state::create_bootstrap_leader_account(&vote_id, &Pubkey::new_rand(), 0, 100);
|
||||||
|
|
||||||
assert_eq!(vote_state.votes.len(), 1);
|
assert_eq!(vote_state.votes.len(), 1);
|
||||||
assert_eq!(vote_state.votes[0], Lockout::new(&Vote::new(0)));
|
assert_eq!(vote_state.votes[0], Lockout::new(&Vote::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -374,21 +437,62 @@ mod tests {
|
||||||
fn test_vote() {
|
fn test_vote() {
|
||||||
let (vote_id, mut vote_account) = create_test_account();
|
let (vote_id, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote = Vote::new(1);
|
let vote = Vote::new(1, Hash::default());
|
||||||
let vote_state = vote_state::vote(&vote_id, &mut vote_account, &vote).unwrap();
|
let vote_state =
|
||||||
|
simulate_process_vote_unchecked(&vote_id, &mut vote_account, &vote).unwrap();
|
||||||
assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]);
|
assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]);
|
||||||
assert_eq!(vote_state.credits(), 0);
|
assert_eq!(vote_state.credits(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vote_slot_hashes() {
|
||||||
|
let (vote_id, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
|
let hash = hash(&[0u8]);
|
||||||
|
let vote = Vote::new(0, hash);
|
||||||
|
|
||||||
|
// wrong hash
|
||||||
|
let vote_state =
|
||||||
|
simulate_process_vote(&vote_id, &mut vote_account, &vote, &[(0, Hash::default())])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
|
// wrong slot
|
||||||
|
let vote_state =
|
||||||
|
simulate_process_vote(&vote_id, &mut vote_account, &vote, &[(1, hash)]).unwrap();
|
||||||
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
|
// empty slot_hashes
|
||||||
|
let vote_state = simulate_process_vote(&vote_id, &mut vote_account, &vote, &[]).unwrap();
|
||||||
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
|
// this one would work, but the wrong account is passed for slot_hashes_id
|
||||||
|
let (_slot_hashes_id, mut slot_hashes_account) =
|
||||||
|
create_test_slot_hashes_account(&[(vote.slot, vote.hash)]);
|
||||||
|
assert_eq!(
|
||||||
|
process_votes(
|
||||||
|
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
||||||
|
&mut KeyedAccount::new(&Pubkey::default(), false, &mut slot_hashes_account),
|
||||||
|
&[],
|
||||||
|
&[vote.clone()],
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidArgument)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vote_signature() {
|
fn test_vote_signature() {
|
||||||
let (vote_id, mut vote_account) = create_test_account();
|
let (vote_id, mut vote_account) = create_test_account();
|
||||||
|
|
||||||
let vote = vec![Vote::new(1)];
|
let vote = vec![Vote::new(1, Hash::default())];
|
||||||
|
|
||||||
|
let (slot_hashes_id, mut slot_hashes_account) =
|
||||||
|
create_test_slot_hashes_account(&[(1, Hash::default())]);
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
||||||
|
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&vote,
|
||||||
);
|
);
|
||||||
|
@ -397,6 +501,7 @@ mod tests {
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
||||||
|
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&vote,
|
||||||
);
|
);
|
||||||
|
@ -430,18 +535,22 @@ mod tests {
|
||||||
assert_eq!(res, Ok(()));
|
assert_eq!(res, Ok(()));
|
||||||
|
|
||||||
// not signed by authorized voter
|
// not signed by authorized voter
|
||||||
let vote = vec![Vote::new(2)];
|
let vote = vec![Vote::new(2, Hash::default())];
|
||||||
|
let (slot_hashes_id, mut slot_hashes_account) =
|
||||||
|
create_test_slot_hashes_account(&[(2, Hash::default())]);
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
||||||
|
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
||||||
&[],
|
&[],
|
||||||
&vote,
|
&vote,
|
||||||
);
|
);
|
||||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||||
|
|
||||||
// signed by authorized voter
|
// signed by authorized voter
|
||||||
let vote = vec![Vote::new(2)];
|
let vote = vec![Vote::new(2, Hash::default())];
|
||||||
let res = process_votes(
|
let res = process_votes(
|
||||||
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
||||||
|
&mut KeyedAccount::new(&slot_hashes_id, false, &mut slot_hashes_account),
|
||||||
&[KeyedAccount::new(
|
&[KeyedAccount::new(
|
||||||
&authorized_voter_id,
|
&authorized_voter_id,
|
||||||
true,
|
true,
|
||||||
|
@ -457,7 +566,11 @@ mod tests {
|
||||||
let vote_id = Pubkey::new_rand();
|
let vote_id = Pubkey::new_rand();
|
||||||
let mut vote_account = Account::new(100, VoteState::size_of(), &id());
|
let mut vote_account = Account::new(100, VoteState::size_of(), &id());
|
||||||
|
|
||||||
let res = vote_state::vote(&vote_id, &mut vote_account, &Vote::new(1));
|
let res = simulate_process_vote_unchecked(
|
||||||
|
&vote_id,
|
||||||
|
&mut vote_account,
|
||||||
|
&Vote::new(1, Hash::default()),
|
||||||
|
);
|
||||||
assert_eq!(res, Err(InstructionError::UninitializedAccount));
|
assert_eq!(res, Err(InstructionError::UninitializedAccount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,7 +581,7 @@ mod tests {
|
||||||
let mut vote_state: VoteState = vote_account.state().unwrap();
|
let mut vote_state: VoteState = vote_account.state().unwrap();
|
||||||
|
|
||||||
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
||||||
vote_state.process_vote(&Vote::new((INITIAL_LOCKOUT as usize * i) as u64));
|
vote_state.process_slot_vote_unchecked((INITIAL_LOCKOUT as usize * i) as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
|
// The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
|
||||||
|
@ -480,14 +593,11 @@ mod tests {
|
||||||
// the root_slot should change to the
|
// the root_slot should change to the
|
||||||
// second vote
|
// second vote
|
||||||
let top_vote = vote_state.votes.front().unwrap().slot;
|
let top_vote = vote_state.votes.front().unwrap().slot;
|
||||||
vote_state.process_vote(&Vote::new(
|
vote_state.process_slot_vote_unchecked(vote_state.votes.back().unwrap().expiration_slot());
|
||||||
vote_state.votes.back().unwrap().expiration_slot(),
|
|
||||||
));
|
|
||||||
assert_eq!(Some(top_vote), vote_state.root_slot);
|
assert_eq!(Some(top_vote), vote_state.root_slot);
|
||||||
|
|
||||||
// Expire everything except the first vote
|
// Expire everything except the first vote
|
||||||
let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot());
|
vote_state.process_slot_vote_unchecked(vote_state.votes.front().unwrap().expiration_slot());
|
||||||
vote_state.process_vote(&vote);
|
|
||||||
// First vote and new vote are both stored for a total of 2 votes
|
// First vote and new vote are both stored for a total of 2 votes
|
||||||
assert_eq!(vote_state.votes.len(), 2);
|
assert_eq!(vote_state.votes.len(), 2);
|
||||||
}
|
}
|
||||||
|
@ -498,8 +608,7 @@ mod tests {
|
||||||
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
||||||
|
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let vote = Vote::new(i as u64);
|
vote_state.process_slot_vote_unchecked(i as u64);
|
||||||
vote_state.process_vote(&vote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_lockouts(&vote_state);
|
check_lockouts(&vote_state);
|
||||||
|
@ -507,17 +616,17 @@ mod tests {
|
||||||
// Expire the third vote (which was a vote for slot 2). The height of the
|
// Expire the third vote (which was a vote for slot 2). The height of the
|
||||||
// vote stack is unchanged, so none of the previous votes should have
|
// vote stack is unchanged, so none of the previous votes should have
|
||||||
// doubled in lockout
|
// doubled in lockout
|
||||||
vote_state.process_vote(&Vote::new((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 lockouts should
|
||||||
// double for everybody
|
// double for everybody
|
||||||
vote_state.process_vote(&Vote::new((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 lockouts should
|
||||||
// double for everybody
|
// double for everybody
|
||||||
vote_state.process_vote(&Vote::new((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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,15 +636,14 @@ mod tests {
|
||||||
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
||||||
|
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let vote = Vote::new(i as u64);
|
vote_state.process_slot_vote_unchecked(i as u64);
|
||||||
vote_state.process_vote(&vote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
||||||
|
|
||||||
// Expire the second and third votes
|
// Expire the second and third votes
|
||||||
let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1;
|
let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1;
|
||||||
vote_state.process_vote(&Vote::new(expire_slot));
|
vote_state.process_slot_vote_unchecked(expire_slot);
|
||||||
assert_eq!(vote_state.votes.len(), 2);
|
assert_eq!(vote_state.votes.len(), 2);
|
||||||
|
|
||||||
// Check that the old votes expired
|
// Check that the old votes expired
|
||||||
|
@ -543,7 +651,7 @@ mod tests {
|
||||||
assert_eq!(vote_state.votes[1].slot, expire_slot);
|
assert_eq!(vote_state.votes[1].slot, expire_slot);
|
||||||
|
|
||||||
// Process one more vote
|
// Process one more vote
|
||||||
vote_state.process_vote(&Vote::new(expire_slot + 1));
|
vote_state.process_slot_vote_unchecked(expire_slot + 1);
|
||||||
|
|
||||||
// Confirmation count for the older first vote should remain unchanged
|
// Confirmation count for the older first vote should remain unchanged
|
||||||
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
assert_eq!(vote_state.votes[0].confirmation_count, 3);
|
||||||
|
@ -559,16 +667,16 @@ mod tests {
|
||||||
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
||||||
|
|
||||||
for i in 0..MAX_LOCKOUT_HISTORY {
|
for i in 0..MAX_LOCKOUT_HISTORY {
|
||||||
vote_state.process_vote(&Vote::new(i as u64));
|
vote_state.process_slot_vote_unchecked(i as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(vote_state.credits, 0);
|
assert_eq!(vote_state.credits, 0);
|
||||||
|
|
||||||
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1));
|
vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 1);
|
||||||
assert_eq!(vote_state.credits, 1);
|
assert_eq!(vote_state.credits, 1);
|
||||||
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2));
|
vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 2);
|
||||||
assert_eq!(vote_state.credits(), 2);
|
assert_eq!(vote_state.credits(), 2);
|
||||||
vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3));
|
vote_state.process_slot_vote_unchecked(MAX_LOCKOUT_HISTORY as u64 + 3);
|
||||||
assert_eq!(vote_state.credits(), 3);
|
assert_eq!(vote_state.credits(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,9 +684,9 @@ mod tests {
|
||||||
fn test_duplicate_vote() {
|
fn test_duplicate_vote() {
|
||||||
let voter_id = Pubkey::new_rand();
|
let voter_id = Pubkey::new_rand();
|
||||||
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
||||||
vote_state.process_vote(&Vote::new(0));
|
vote_state.process_slot_vote_unchecked(0);
|
||||||
vote_state.process_vote(&Vote::new(1));
|
vote_state.process_slot_vote_unchecked(1);
|
||||||
vote_state.process_vote(&Vote::new(0));
|
vote_state.process_slot_vote_unchecked(0);
|
||||||
assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1);
|
assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1);
|
||||||
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
|
assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
|
||||||
assert!(vote_state.nth_recent_vote(2).is_none());
|
assert!(vote_state.nth_recent_vote(2).is_none());
|
||||||
|
@ -589,7 +697,7 @@ mod tests {
|
||||||
let voter_id = Pubkey::new_rand();
|
let voter_id = Pubkey::new_rand();
|
||||||
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0);
|
||||||
for i in 0..MAX_LOCKOUT_HISTORY {
|
for i in 0..MAX_LOCKOUT_HISTORY {
|
||||||
vote_state.process_vote(&Vote::new(i as u64));
|
vote_state.process_slot_vote_unchecked(i as u64);
|
||||||
}
|
}
|
||||||
for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
|
for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -613,7 +721,7 @@ mod tests {
|
||||||
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))
|
.map(|i| Vote::new(vote_state.votes.get(i).unwrap().slot, Hash::default()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,17 +734,20 @@ mod tests {
|
||||||
let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0);
|
let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0);
|
||||||
|
|
||||||
// process some votes on account a
|
// process some votes on account a
|
||||||
let votes_a: Vec<_> = (0..5).into_iter().map(|i| Vote::new(i)).collect();
|
(0..5)
|
||||||
vote_state_a.process_votes(&votes_a);
|
.into_iter()
|
||||||
|
.for_each(|i| vote_state_a.process_slot_vote_unchecked(i as u64));
|
||||||
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 votes: Vec<_> = (0..MAX_RECENT_VOTES)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i| Vote::new(i as u64))
|
.map(|i| Vote::new(i as u64, Hash::default()))
|
||||||
.collect();
|
.collect();
|
||||||
vote_state_a.process_votes(&votes);
|
let slot_hashes: Vec<_> = votes.iter().map(|vote| (vote.slot, vote.hash)).collect();
|
||||||
vote_state_b.process_votes(&votes);
|
|
||||||
|
vote_state_a.process_votes(&votes, &slot_hashes);
|
||||||
|
vote_state_b.process_votes(&votes, &slot_hashes);
|
||||||
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
/// Maximum over-the-wire size of a Transaction
|
/// Maximum over-the-wire size of a Transaction
|
||||||
pub const PACKET_DATA_SIZE: usize = 512;
|
/// 1280 is IPv6 minimum MTU
|
||||||
|
/// 40 bytes is the size of the IPv6 header
|
||||||
|
/// 8 bytes is the size of the fragment header
|
||||||
|
pub const PACKET_DATA_SIZE: usize = 1280 - 40 - 8;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
use crate::pubkey::Pubkey;
|
use crate::pubkey::Pubkey;
|
||||||
|
|
||||||
pub mod slothashes;
|
pub mod slot_hashes;
|
||||||
|
|
||||||
/// "Sysca11111111111111111111111111111111111111"
|
/// "Sysca11111111111111111111111111111111111111"
|
||||||
/// owner pubkey for syscall accounts
|
/// owner pubkey for syscall accounts
|
||||||
|
|
|
@ -17,6 +17,10 @@ pub fn id() -> Pubkey {
|
||||||
Pubkey::new(&SYSCALL_SLOT_HASHES_ID)
|
Pubkey::new(&SYSCALL_SLOT_HASHES_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_id(pubkey: &Pubkey) -> bool {
|
||||||
|
pubkey.as_ref() == SYSCALL_SLOT_HASHES_ID
|
||||||
|
}
|
||||||
|
|
||||||
use crate::account_utils::State;
|
use crate::account_utils::State;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
Loading…
Reference in New Issue