solana/src/vote_stage.rs

441 lines
13 KiB
Rust

//! The `vote_stage` votes on the `last_id` of the bank at a regular cadence
use bank::Bank;
use bincode::serialize;
use counter::Counter;
use crdt::Crdt;
use hash::Hash;
use influx_db_client as influxdb;
use log::Level;
use metrics;
use packet::{BlobRecycler, SharedBlob};
use result::Result;
use service::Service;
use signature::{Keypair, Pubkey};
use std::result;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::{self, sleep, spawn, JoinHandle};
use std::time::Duration;
use streamer::BlobSender;
use timing;
use transaction::Transaction;
pub const VOTE_TIMEOUT_MS: u64 = 1000;
pub struct VoteStage {
thread_hdl: JoinHandle<()>,
}
#[derive(Debug, PartialEq, Eq)]
enum VoteError {
NoValidLastIdsToVoteOn,
}
pub fn create_new_signed_vote_blob(
last_id: &Hash,
keypair: &Keypair,
crdt: &Arc<RwLock<Crdt>>,
blob_recycler: &BlobRecycler,
) -> Result<SharedBlob> {
let shared_blob = blob_recycler.allocate();
let (vote, addr) = {
let mut wcrdt = crdt.write().unwrap();
//TODO: doesn't seem like there is a synchronous call to get height and id
debug!("voting on {:?}", &last_id.as_ref()[..8]);
wcrdt.new_vote(*last_id)
}?;
let tx = Transaction::budget_new_vote(&keypair, vote, *last_id, 0);
{
let mut blob = shared_blob.write().unwrap();
let bytes = serialize(&tx)?;
let len = bytes.len();
blob.data[..len].copy_from_slice(&bytes);
blob.meta.set_addr(&addr);
blob.meta.size = len;
}
Ok(shared_blob)
}
fn get_last_id_to_vote_on(
id: &Pubkey,
ids: &[Hash],
bank: &Arc<Bank>,
now: u64,
last_vote: &mut u64,
last_valid_validator_timestamp: &mut u64,
) -> result::Result<(Hash, u64), VoteError> {
let mut valid_ids = bank.count_valid_ids(&ids);
let super_majority_index = (2 * ids.len()) / 3;
//TODO(anatoly): this isn't stake based voting
debug!(
"{}: valid_ids {}/{} {}",
id,
valid_ids.len(),
ids.len(),
super_majority_index,
);
metrics::submit(
influxdb::Point::new("vote_stage-peer_count")
.add_field("total_peers", influxdb::Value::Integer(ids.len() as i64))
.add_field(
"valid_peers",
influxdb::Value::Integer(valid_ids.len() as i64),
).to_owned(),
);
if valid_ids.len() > super_majority_index {
*last_vote = now;
// Sort by timestamp
valid_ids.sort_by(|a, b| a.1.cmp(&b.1));
let last_id = ids[valid_ids[super_majority_index].0];
return Ok((last_id, valid_ids[super_majority_index].1));
}
if *last_valid_validator_timestamp != 0 {
metrics::submit(
influxdb::Point::new(&"leader-finality")
.add_field(
"duration_ms",
influxdb::Value::Integer((now - *last_valid_validator_timestamp) as i64),
).to_owned(),
);
}
Err(VoteError::NoValidLastIdsToVoteOn)
}
pub fn send_leader_vote(
id: &Pubkey,
keypair: &Keypair,
bank: &Arc<Bank>,
crdt: &Arc<RwLock<Crdt>>,
blob_recycler: &BlobRecycler,
vote_blob_sender: &BlobSender,
last_vote: &mut u64,
last_valid_validator_timestamp: &mut u64,
) -> Result<()> {
let now = timing::timestamp();
if now - *last_vote > VOTE_TIMEOUT_MS {
let ids: Vec<_> = crdt.read().unwrap().valid_last_ids();
if let Ok((last_id, super_majority_timestamp)) = get_last_id_to_vote_on(
id,
&ids,
bank,
now,
last_vote,
last_valid_validator_timestamp,
) {
if let Ok(shared_blob) =
create_new_signed_vote_blob(&last_id, keypair, crdt, blob_recycler)
{
vote_blob_sender.send(vec![shared_blob])?;
let finality_ms = now - super_majority_timestamp;
*last_valid_validator_timestamp = super_majority_timestamp;
debug!("{} leader_sent_vote finality: {} ms", id, finality_ms);
inc_new_counter_info!("vote_stage-leader_sent_vote", 1);
bank.set_finality((now - *last_valid_validator_timestamp) as usize);
metrics::submit(
influxdb::Point::new(&"leader-finality")
.add_field("duration_ms", influxdb::Value::Integer(finality_ms as i64))
.to_owned(),
);
}
}
}
Ok(())
}
fn send_validator_vote(
bank: &Arc<Bank>,
keypair: &Arc<Keypair>,
crdt: &Arc<RwLock<Crdt>>,
blob_recycler: &BlobRecycler,
vote_blob_sender: &BlobSender,
) -> Result<()> {
let last_id = bank.last_id();
if let Ok(shared_blob) = create_new_signed_vote_blob(&last_id, keypair, crdt, blob_recycler) {
inc_new_counter_info!("replicate-vote_sent", 1);
vote_blob_sender.send(vec![shared_blob])?;
}
Ok(())
}
impl VoteStage {
pub fn new(
keypair: Arc<Keypair>,
bank: Arc<Bank>,
crdt: Arc<RwLock<Crdt>>,
blob_recycler: BlobRecycler,
vote_blob_sender: BlobSender,
exit: Arc<AtomicBool>,
) -> Self {
let thread_hdl = spawn(move || {
Self::run(
&keypair,
&bank,
&crdt,
&blob_recycler,
&vote_blob_sender,
&exit,
);
});
VoteStage { thread_hdl }
}
fn run(
keypair: &Arc<Keypair>,
bank: &Arc<Bank>,
crdt: &Arc<RwLock<Crdt>>,
blob_recycler: &BlobRecycler,
vote_blob_sender: &BlobSender,
exit: &Arc<AtomicBool>,
) {
while !exit.load(Ordering::Relaxed) {
if let Err(err) =
send_validator_vote(bank, keypair, crdt, blob_recycler, vote_blob_sender)
{
info!("Vote failed: {:?}", err);
}
sleep(Duration::from_millis(VOTE_TIMEOUT_MS));
}
}
}
impl Service for VoteStage {
type JoinReturnType = ();
fn join(self) -> thread::Result<()> {
self.thread_hdl.join()?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use bank::Bank;
use bincode::deserialize;
use crdt::{Crdt, Node, NodeInfo};
use entry::next_entry;
use hash::{hash, Hash};
use instruction::Vote;
use logger;
use mint::Mint;
use packet::BlobRecycler;
use service::Service;
use signature::{Keypair, KeypairUtil};
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use transaction::Transaction;
/// Ensure the VoteStage issues votes at the expected cadence
#[test]
fn test_vote_cadence() {
let keypair = Keypair::new();
let mint = Mint::new(1234);
let bank = Arc::new(Bank::new(&mint));
let node = Node::new_localhost();
let mut crdt = Crdt::new(node.info.clone()).expect("Crdt::new");
crdt.set_leader(node.info.id);
let blob_recycler = BlobRecycler::default();
let (sender, receiver) = channel();
let exit = Arc::new(AtomicBool::new(false));
let vote_stage = VoteStage::new(
Arc::new(keypair),
bank.clone(),
Arc::new(RwLock::new(crdt)),
blob_recycler.clone(),
sender,
exit.clone(),
);
receiver.recv().unwrap();
let timeout = Duration::from_millis(VOTE_TIMEOUT_MS * 2);
receiver.recv_timeout(timeout).unwrap();
receiver.recv_timeout(timeout).unwrap();
exit.store(true, Ordering::Relaxed);
vote_stage.join().expect("join");
}
#[test]
fn test_send_leader_vote() {
logger::setup();
// create a mint/bank
let mint = Mint::new(1000);
let bank = Arc::new(Bank::new(&mint));
let hash0 = Hash::default();
// get a non-default hash last_id
let entry = next_entry(&hash0, 1, vec![]);
bank.register_entry_id(&entry.id);
// Create a leader
let leader_data = NodeInfo::new_with_socketaddr(&"127.0.0.1:1234".parse().unwrap());
let leader_pubkey = leader_data.id.clone();
let mut leader_crdt = Crdt::new(leader_data).unwrap();
// give the leader some tokens
let give_leader_tokens_tx =
Transaction::system_new(&mint.keypair(), leader_pubkey.clone(), 100, entry.id);
bank.process_transaction(&give_leader_tokens_tx).unwrap();
leader_crdt.set_leader(leader_pubkey);
// Insert 7 agreeing validators / 3 disagreeing
// and votes for new last_id
for i in 0..10 {
let mut validator =
NodeInfo::new_with_socketaddr(&format!("127.0.0.1:234{}", i).parse().unwrap());
let vote = Vote {
version: validator.version + 1,
contact_info_version: 1,
};
if i < 7 {
validator.ledger_state.last_id = entry.id;
}
leader_crdt.insert(&validator);
trace!("validator id: {:?}", validator.id);
leader_crdt.insert_vote(&validator.id, &vote, entry.id);
}
let leader = Arc::new(RwLock::new(leader_crdt));
let blob_recycler = BlobRecycler::default();
let (vote_blob_sender, vote_blob_receiver) = channel();
let mut last_vote: u64 = timing::timestamp() - VOTE_TIMEOUT_MS - 1;
let mut last_valid_validator_timestamp = 0;
let res = send_leader_vote(
&mint.pubkey(),
&mint.keypair(),
&bank,
&leader,
&blob_recycler,
&vote_blob_sender,
&mut last_vote,
&mut last_valid_validator_timestamp,
);
trace!("vote result: {:?}", res);
assert!(res.is_ok());
let vote_blob = vote_blob_receiver.recv_timeout(Duration::from_millis(500));
trace!("vote_blob: {:?}", vote_blob);
// leader shouldn't vote yet, not enough votes
assert!(vote_blob.is_err());
// add two more nodes and see that it succeeds
for i in 0..2 {
let mut validator =
NodeInfo::new_with_socketaddr(&format!("127.0.0.1:234{}", i).parse().unwrap());
let vote = Vote {
version: validator.version + 1,
contact_info_version: 1,
};
validator.ledger_state.last_id = entry.id;
leader.write().unwrap().insert(&validator);
trace!("validator id: {:?}", validator.id);
leader
.write()
.unwrap()
.insert_vote(&validator.id, &vote, entry.id);
}
last_vote = timing::timestamp() - VOTE_TIMEOUT_MS - 1;
let res = send_leader_vote(
&Pubkey::default(),
&mint.keypair(),
&bank,
&leader,
&blob_recycler,
&vote_blob_sender,
&mut last_vote,
&mut last_valid_validator_timestamp,
);
trace!("vote result: {:?}", res);
assert!(res.is_ok());
let vote_blob = vote_blob_receiver.recv_timeout(Duration::from_millis(500));
trace!("vote_blob: {:?}", vote_blob);
// leader should vote now
assert!(vote_blob.is_ok());
// vote should be valid
let blob = &vote_blob.unwrap()[0];
let tx = deserialize(&(blob.read().unwrap().data)).unwrap();
assert!(bank.process_transaction(&tx).is_ok());
}
#[test]
fn test_get_last_id_to_vote_on() {
logger::setup();
let mint = Mint::new(1234);
let bank = Arc::new(Bank::new(&mint));
let mut last_vote = 0;
let mut last_valid_validator_timestamp = 0;
// generate 10 last_ids, register 6 with the bank
let ids: Vec<_> = (0..10)
.map(|i| {
let last_id = hash(&serialize(&i).unwrap()); // Unique hash
if i < 6 {
bank.register_entry_id(&last_id);
}
// sleep to get a different timestamp in the bank
sleep(Duration::from_millis(1));
last_id
}).collect();
// see that we fail to have 2/3rds consensus
assert!(
get_last_id_to_vote_on(
&Pubkey::default(),
&ids,
&bank,
0,
&mut last_vote,
&mut last_valid_validator_timestamp
).is_err()
);
// register another, see passing
bank.register_entry_id(&ids[6]);
let res = get_last_id_to_vote_on(
&Pubkey::default(),
&ids,
&bank,
0,
&mut last_vote,
&mut last_valid_validator_timestamp,
);
if let Ok((hash, timestamp)) = res {
assert!(hash == ids[6]);
assert!(timestamp != 0);
} else {
assert!(false, "get_last_id returned error!: {:?}", res);
}
}
}