Send recent votes in Vote Transactions (#3734)
This commit is contained in:
@ -475,7 +475,7 @@ mod tests {
|
|||||||
|
|
||||||
fn create_sample_vote(keypair: &Keypair, hash: Hash) -> Transaction {
|
fn create_sample_vote(keypair: &Keypair, hash: Hash) -> Transaction {
|
||||||
let pubkey = keypair.pubkey();
|
let pubkey = keypair.pubkey();
|
||||||
let ix = vote_instruction::vote(&pubkey, Vote::new(1));
|
let ix = vote_instruction::vote(&pubkey, vec![Vote::new(1)]);
|
||||||
Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
|
Transaction::new_signed_instructions(&[keypair], vec![ix], hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +347,8 @@ pub fn make_active_set_entries(
|
|||||||
let new_vote_account_entry = next_entry_mut(&mut last_entry_hash, 1, vec![new_vote_account_tx]);
|
let new_vote_account_entry = next_entry_mut(&mut last_entry_hash, 1, vec![new_vote_account_tx]);
|
||||||
|
|
||||||
// 3) Create vote entry
|
// 3) Create vote entry
|
||||||
let vote_ix = vote_instruction::vote(&voting_keypair.pubkey(), Vote::new(slot_to_vote_on));
|
let vote_ix =
|
||||||
|
vote_instruction::vote(&voting_keypair.pubkey(), vec![Vote::new(slot_to_vote_on)]);
|
||||||
let vote_tx =
|
let vote_tx =
|
||||||
Transaction::new_signed_instructions(&[&voting_keypair], vec![vote_ix], *blockhash);
|
Transaction::new_signed_instructions(&[&voting_keypair], vec![vote_ix], *blockhash);
|
||||||
let vote_entry = next_entry_mut(&mut last_entry_hash, 1, vec![vote_tx]);
|
let vote_entry = next_entry_mut(&mut last_entry_hash, 1, vec![vote_tx]);
|
||||||
|
@ -10,6 +10,7 @@ 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;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EpochStakes {
|
pub struct EpochStakes {
|
||||||
@ -253,6 +254,13 @@ impl Locktower {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn recent_votes(&self) -> Vec<Vote> {
|
||||||
|
let start = self.lockouts.votes.len().saturating_sub(MAX_RECENT_VOTES);
|
||||||
|
(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> {
|
||||||
self.lockouts.root_slot
|
self.lockouts.root_slot
|
||||||
}
|
}
|
||||||
@ -798,4 +806,29 @@ mod test {
|
|||||||
locktower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
locktower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
||||||
assert!(!locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
assert!(!locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vote_and_check_recent(num_votes: usize) {
|
||||||
|
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
|
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
||||||
|
let expected: Vec<_> = (start..num_votes).map(|i| Vote::new(i as u64)).collect();
|
||||||
|
for i in 0..num_votes {
|
||||||
|
locktower.record_vote(i as u64);
|
||||||
|
}
|
||||||
|
assert_eq!(expected, locktower.recent_votes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recent_votes_full() {
|
||||||
|
vote_and_check_recent(MAX_LOCKOUT_HISTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recent_votes_empty() {
|
||||||
|
vote_and_check_recent(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recent_votes_exact() {
|
||||||
|
vote_and_check_recent(MAX_RECENT_VOTES)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ use solana_sdk::signature::KeypairUtil;
|
|||||||
use solana_sdk::timing::{self, duration_as_ms};
|
use solana_sdk::timing::{self, duration_as_ms};
|
||||||
use solana_sdk::transaction::Transaction;
|
use solana_sdk::transaction::Transaction;
|
||||||
use solana_vote_api::vote_instruction;
|
use solana_vote_api::vote_instruction;
|
||||||
use solana_vote_api::vote_state::Vote;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
@ -294,24 +293,25 @@ impl ReplayStage {
|
|||||||
locktower: &mut Locktower,
|
locktower: &mut Locktower,
|
||||||
progress: &mut HashMap<u64, ForkProgress>,
|
progress: &mut HashMap<u64, ForkProgress>,
|
||||||
voting_keypair: &Option<Arc<T>>,
|
voting_keypair: &Option<Arc<T>>,
|
||||||
vote_account: &Pubkey,
|
vote_account_pubkey: &Pubkey,
|
||||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||||
blocktree: &Arc<Blocktree>,
|
blocktree: &Arc<Blocktree>,
|
||||||
) where
|
) where
|
||||||
T: 'static + KeypairUtil + Send + Sync,
|
T: 'static + KeypairUtil + Send + Sync,
|
||||||
{
|
{
|
||||||
if let Some(ref voting_keypair) = voting_keypair {
|
if let Some(ref voting_keypair) = voting_keypair {
|
||||||
let vote_ix = vote_instruction::vote(&vote_account, Vote::new(bank.slot()));
|
|
||||||
let vote_tx = Transaction::new_signed_instructions(
|
|
||||||
&[voting_keypair.as_ref()],
|
|
||||||
vec![vote_ix],
|
|
||||||
bank.last_blockhash(),
|
|
||||||
);
|
|
||||||
if let Some(new_root) = locktower.record_vote(bank.slot()) {
|
if let Some(new_root) = locktower.record_vote(bank.slot()) {
|
||||||
bank_forks.write().unwrap().set_root(new_root);
|
bank_forks.write().unwrap().set_root(new_root);
|
||||||
blocktree.set_root(new_root);
|
blocktree.set_root(new_root);
|
||||||
Self::handle_new_root(&bank_forks, progress);
|
Self::handle_new_root(&bank_forks, progress);
|
||||||
}
|
}
|
||||||
|
// Send our last few votes along with the new one
|
||||||
|
let vote_ix = vote_instruction::vote(vote_account_pubkey, locktower.recent_votes());
|
||||||
|
let vote_tx = Transaction::new_signed_instructions(
|
||||||
|
&[voting_keypair.as_ref()],
|
||||||
|
vec![vote_ix],
|
||||||
|
bank.last_blockhash(),
|
||||||
|
);
|
||||||
locktower.update_epoch(&bank);
|
locktower.update_epoch(&bank);
|
||||||
cluster_info.write().unwrap().push_vote(vote_tx);
|
cluster_info.write().unwrap().push_vote(vote_tx);
|
||||||
}
|
}
|
||||||
@ -604,6 +604,7 @@ mod test {
|
|||||||
use solana_sdk::genesis_block::GenesisBlock;
|
use solana_sdk::genesis_block::GenesisBlock;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_vote_api::vote_state::Vote;
|
||||||
use std::fs::remove_dir_all;
|
use std::fs::remove_dir_all;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
@ -652,8 +653,7 @@ mod test {
|
|||||||
&poh_recorder,
|
&poh_recorder,
|
||||||
ledger_writer_sender,
|
ledger_writer_sender,
|
||||||
);
|
);
|
||||||
|
let vote_ix = vote_instruction::vote(&voting_keypair.pubkey(), vec![Vote::new(0)]);
|
||||||
let vote_ix = vote_instruction::vote(&voting_keypair.pubkey(), Vote::new(0));
|
|
||||||
let vote_tx = Transaction::new_signed_instructions(
|
let vote_tx = Transaction::new_signed_instructions(
|
||||||
&[voting_keypair.as_ref()],
|
&[voting_keypair.as_ref()],
|
||||||
vec![vote_ix],
|
vec![vote_ix],
|
||||||
|
@ -138,7 +138,7 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot: u64) {
|
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot: u64) {
|
||||||
let ix = vote_instruction::vote(&voting_keypair.pubkey(), Vote::new(slot));
|
let ix = vote_instruction::vote(&voting_keypair.pubkey(), vec![Vote::new(slot)]);
|
||||||
process_instructions(bank, &[voting_keypair], vec![ix]);
|
process_instructions(bank, &[voting_keypair], vec![ix]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,10 @@ pub mod tests {
|
|||||||
0,
|
0,
|
||||||
lamports,
|
lamports,
|
||||||
);
|
);
|
||||||
ixs.push(vote_instruction::vote(&voting_pubkey, Vote::new(slot)));
|
ixs.push(vote_instruction::vote(
|
||||||
|
&voting_pubkey,
|
||||||
|
vec![Vote::new(slot)],
|
||||||
|
));
|
||||||
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
process_instructions(bank, &[from_keypair, voting_keypair], ixs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ pub enum VoteInstruction {
|
|||||||
/// Authorize a voter to send signed votes.
|
/// Authorize a voter to send signed votes.
|
||||||
AuthorizeVoter(Pubkey),
|
AuthorizeVoter(Pubkey),
|
||||||
|
|
||||||
Vote(Vote),
|
/// A Vote instruction with recent votes
|
||||||
|
Vote(Vec<Vote>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction {
|
fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction {
|
||||||
@ -54,9 +55,9 @@ pub fn authorize_voter(vote_id: &Pubkey, authorized_voter_id: &Pubkey) -> Instru
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vote(vote_id: &Pubkey, vote: Vote) -> Instruction {
|
pub fn vote(vote_id: &Pubkey, recent_votes: Vec<Vote>) -> Instruction {
|
||||||
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
let account_metas = vec![AccountMeta::new(*vote_id, true)];
|
||||||
Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas)
|
Instruction::new(id(), &VoteInstruction::Vote(recent_votes), account_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_instruction(
|
pub fn process_instruction(
|
||||||
@ -144,7 +145,7 @@ mod tests {
|
|||||||
vote_keypair: &Keypair,
|
vote_keypair: &Keypair,
|
||||||
tick_height: u64,
|
tick_height: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), Vote::new(tick_height));
|
let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), vec![Vote::new(tick_height)]);
|
||||||
bank_client
|
bank_client
|
||||||
.send_instruction(vote_keypair, vote_ix)
|
.send_instruction(vote_keypair, vote_ix)
|
||||||
.map_err(|err| err.unwrap())?;
|
.map_err(|err| err.unwrap())?;
|
||||||
@ -195,7 +196,7 @@ mod tests {
|
|||||||
create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap();
|
create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap();
|
||||||
|
|
||||||
let mallory_id = mallory_keypair.pubkey();
|
let mallory_id = mallory_keypair.pubkey();
|
||||||
let mut vote_ix = vote_instruction::vote(&vote_id, Vote::new(0));
|
let mut vote_ix = vote_instruction::vote(&vote_id, vec![Vote::new(0)]);
|
||||||
vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required.
|
vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required.
|
||||||
|
|
||||||
// Sneak in an instruction so that the transaction is signed but
|
// Sneak in an instruction so that the transaction is signed but
|
||||||
|
@ -117,6 +117,10 @@ impl VoteState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_votes(&mut self, votes: &[Vote]) {
|
||||||
|
votes.iter().for_each(|v| self.process_vote(v));;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_vote(&mut self, vote: &Vote) {
|
pub fn process_vote(&mut self, vote: &Vote) {
|
||||||
// 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
|
||||||
@ -227,7 +231,7 @@ pub fn initialize_account(
|
|||||||
pub fn process_vote(
|
pub fn process_vote(
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
other_signers: &[KeyedAccount],
|
other_signers: &[KeyedAccount],
|
||||||
vote: &Vote,
|
votes: &[Vote],
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let mut vote_state: VoteState = vote_account.state()?;
|
let mut vote_state: VoteState = vote_account.state()?;
|
||||||
|
|
||||||
@ -245,7 +249,7 @@ pub fn process_vote(
|
|||||||
return Err(InstructionError::MissingRequiredSignature);
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_state.process_vote(vote);
|
vote_state.process_votes(&votes);
|
||||||
vote_account.set_state(&vote_state)
|
vote_account.set_state(&vote_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +280,7 @@ pub fn vote(
|
|||||||
process_vote(
|
process_vote(
|
||||||
&mut KeyedAccount::new(vote_id, true, vote_account),
|
&mut KeyedAccount::new(vote_id, true, vote_account),
|
||||||
&[],
|
&[],
|
||||||
vote,
|
&[vote.clone()],
|
||||||
)?;
|
)?;
|
||||||
vote_account.state()
|
vote_account.state()
|
||||||
}
|
}
|
||||||
@ -286,6 +290,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::vote_state;
|
use crate::vote_state;
|
||||||
|
|
||||||
|
const MAX_RECENT_VOTES: usize = 16;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_initialize_vote_account() {
|
fn test_initialize_vote_account() {
|
||||||
let vote_account_id = Pubkey::new_rand();
|
let vote_account_id = Pubkey::new_rand();
|
||||||
@ -346,7 +352,7 @@ mod tests {
|
|||||||
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 = Vote::new(1);
|
let vote = vec![Vote::new(1)];
|
||||||
|
|
||||||
// unsigned
|
// unsigned
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
@ -392,7 +398,7 @@ 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);
|
let vote = vec![Vote::new(2)];
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, true, &mut vote_account),
|
||||||
&[],
|
&[],
|
||||||
@ -401,7 +407,7 @@ mod tests {
|
|||||||
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);
|
let vote = vec![Vote::new(2)];
|
||||||
let res = process_vote(
|
let res = process_vote(
|
||||||
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
&mut KeyedAccount::new(&vote_id, false, &mut vote_account),
|
||||||
&[KeyedAccount::new(
|
&[KeyedAccount::new(
|
||||||
@ -571,4 +577,34 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recent_votes(vote_state: &VoteState) -> Vec<Vote> {
|
||||||
|
let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
|
||||||
|
(start..vote_state.votes.len())
|
||||||
|
.map(|i| Vote::new(vote_state.votes.get(i).unwrap().slot))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// check that two accounts with different data can be brought to the same state with one vote submission
|
||||||
|
#[test]
|
||||||
|
fn test_process_missed_votes() {
|
||||||
|
let account_a = Pubkey::new_rand();
|
||||||
|
let mut vote_state_a = VoteState::new(&account_a, &Pubkey::new_rand(), 0);
|
||||||
|
let account_b = Pubkey::new_rand();
|
||||||
|
let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0);
|
||||||
|
|
||||||
|
// process some votes on account a
|
||||||
|
let votes_a: Vec<_> = (0..5).into_iter().map(|i| Vote::new(i)).collect();
|
||||||
|
vote_state_a.process_votes(&votes_a);
|
||||||
|
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
|
||||||
|
let votes: Vec<_> = (0..MAX_RECENT_VOTES)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| Vote::new(i as u64))
|
||||||
|
.collect();
|
||||||
|
vote_state_a.process_votes(&votes);
|
||||||
|
vote_state_b.process_votes(&votes);
|
||||||
|
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user