move tick generation back to banking_stage, add unit tests (#1332)
* move tick generation back to banking_stage, add unit tests fixes #1217 * remove channel() stuff for synchronous comm; use a mutex
This commit is contained in:
parent
680072e5e2
commit
8a7545197f
|
@ -6,10 +6,9 @@ use bank::Bank;
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use hash::{Hash, Hasher};
|
use hash::Hasher;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use packet::{Packets, SharedPackets};
|
use packet::{Packets, SharedPackets};
|
||||||
use poh::PohEntry;
|
|
||||||
use poh_service::PohService;
|
use poh_service::PohService;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use result::{Error, Result};
|
use result::{Error, Result};
|
||||||
|
@ -43,26 +42,39 @@ impl BankingStage {
|
||||||
let thread_hdl = Builder::new()
|
let thread_hdl = Builder::new()
|
||||||
.name("solana-banking-stage".to_string())
|
.name("solana-banking-stage".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let (hash_sender, hash_receiver) = channel();
|
let poh_service = PohService::new(bank.last_id(), tick_duration.is_some());
|
||||||
let (poh_service, poh_receiver) =
|
|
||||||
PohService::new(bank.last_id(), hash_receiver, tick_duration);
|
let mut last_tick = Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let timeout =
|
||||||
|
tick_duration.map(|duration| duration - (Instant::now() - last_tick));
|
||||||
|
|
||||||
if let Err(e) = Self::process_packets(
|
if let Err(e) = Self::process_packets(
|
||||||
|
timeout,
|
||||||
&bank,
|
&bank,
|
||||||
&hash_sender,
|
&poh_service,
|
||||||
&poh_receiver,
|
|
||||||
&verified_receiver,
|
&verified_receiver,
|
||||||
&entry_sender,
|
&entry_sender,
|
||||||
) {
|
) {
|
||||||
match e {
|
match e {
|
||||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break,
|
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break,
|
||||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
||||||
|
Error::RecvError(_) => break,
|
||||||
Error::SendError => break,
|
Error::SendError => break,
|
||||||
_ => error!("{:?}", e),
|
_ => {
|
||||||
|
error!("process_packets() {:?}", e);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(hash_sender);
|
if tick_duration.is_some() && last_tick.elapsed() > tick_duration.unwrap() {
|
||||||
|
if let Err(e) = Self::tick(&poh_service, &entry_sender) {
|
||||||
|
error!("tick() {:?}", e);
|
||||||
|
}
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
poh_service.join().unwrap();
|
poh_service.join().unwrap();
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
(BankingStage { thread_hdl }, entry_receiver)
|
(BankingStage { thread_hdl }, entry_receiver)
|
||||||
|
@ -80,26 +92,33 @@ impl BankingStage {
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tick(poh_service: &PohService, entry_sender: &Sender<Vec<Entry>>) -> Result<()> {
|
||||||
|
let poh = poh_service.tick();
|
||||||
|
let entry = Entry {
|
||||||
|
num_hashes: poh.num_hashes,
|
||||||
|
id: poh.id,
|
||||||
|
transactions: vec![],
|
||||||
|
};
|
||||||
|
entry_sender.send(vec![entry])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn process_transactions(
|
fn process_transactions(
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
transactions: &[Transaction],
|
transactions: &[Transaction],
|
||||||
hash_sender: &Sender<Hash>,
|
poh_service: &PohService,
|
||||||
poh_receiver: &Receiver<PohEntry>,
|
) -> Result<Vec<Entry>> {
|
||||||
entry_sender: &Sender<Vec<Entry>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
debug!("transactions: {}", transactions.len());
|
debug!("processing: {}", transactions.len());
|
||||||
|
|
||||||
let mut chunk_start = 0;
|
let mut chunk_start = 0;
|
||||||
while chunk_start != transactions.len() {
|
while chunk_start != transactions.len() {
|
||||||
let chunk_end = chunk_start + Entry::num_will_fit(&transactions[chunk_start..]);
|
let chunk_end = chunk_start + Entry::num_will_fit(&transactions[chunk_start..]);
|
||||||
|
|
||||||
let results = bank.process_transactions(&transactions[chunk_start..chunk_end]);
|
let results = bank.process_transactions(&transactions[chunk_start..chunk_end]);
|
||||||
debug!("results: {}", results.len());
|
|
||||||
|
|
||||||
let mut hasher = Hasher::default();
|
let mut hasher = Hasher::default();
|
||||||
|
|
||||||
let processed_transactions: Vec<_> = transactions[chunk_start..chunk_end]
|
let processed_transactions: Vec<_> = transactions[chunk_start..chunk_end]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -114,64 +133,45 @@ impl BankingStage {
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
debug!("processed ok: {}", processed_transactions.len());
|
debug!("processed: {}", processed_transactions.len());
|
||||||
|
|
||||||
chunk_start = chunk_end;
|
chunk_start = chunk_end;
|
||||||
|
|
||||||
let hash = hasher.result();
|
let hash = hasher.result();
|
||||||
|
|
||||||
if !processed_transactions.is_empty() {
|
if !processed_transactions.is_empty() {
|
||||||
hash_sender.send(hash)?;
|
let poh = poh_service.record(hash);
|
||||||
|
|
||||||
let mut answered = false;
|
|
||||||
while !answered {
|
|
||||||
entries.extend(poh_receiver.try_iter().map(|poh| {
|
|
||||||
if let Some(mixin) = poh.mixin {
|
|
||||||
answered = true;
|
|
||||||
assert_eq!(mixin, hash);
|
|
||||||
bank.register_entry_id(&poh.id);
|
bank.register_entry_id(&poh.id);
|
||||||
Entry {
|
entries.push(Entry {
|
||||||
num_hashes: poh.num_hashes,
|
num_hashes: poh.num_hashes,
|
||||||
id: poh.id,
|
id: poh.id,
|
||||||
transactions: processed_transactions.clone(),
|
transactions: processed_transactions,
|
||||||
}
|
});
|
||||||
} else {
|
|
||||||
Entry {
|
|
||||||
num_hashes: poh.num_hashes,
|
|
||||||
id: poh.id,
|
|
||||||
transactions: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entries.extend(poh_receiver.try_iter().map(|poh| Entry {
|
|
||||||
num_hashes: poh.num_hashes,
|
|
||||||
id: poh.id,
|
|
||||||
transactions: vec![],
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("done process_transactions, {} entries", entries.len());
|
debug!("done process_transactions, {} entries", entries.len());
|
||||||
|
|
||||||
entry_sender.send(entries)?;
|
Ok(entries)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process the incoming packets and send output `Signal` messages to `signal_sender`.
|
/// Process the incoming packets and send output `Signal` messages to `signal_sender`.
|
||||||
/// Discard packets via `packet_recycler`.
|
/// Discard packets via `packet_recycler`.
|
||||||
pub fn process_packets(
|
pub fn process_packets(
|
||||||
|
timeout: Option<Duration>,
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
hash_sender: &Sender<Hash>,
|
poh_service: &PohService,
|
||||||
poh_receiver: &Receiver<PohEntry>,
|
|
||||||
verified_receiver: &Receiver<Vec<(SharedPackets, Vec<u8>)>>,
|
verified_receiver: &Receiver<Vec<(SharedPackets, Vec<u8>)>>,
|
||||||
entry_sender: &Sender<Vec<Entry>>,
|
entry_sender: &Sender<Vec<Entry>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let timer = Duration::new(1, 0);
|
|
||||||
let recv_start = Instant::now();
|
let recv_start = Instant::now();
|
||||||
let mms = verified_receiver.recv_timeout(timer)?;
|
// TODO pass deadline to recv_deadline() when/if it becomes available?
|
||||||
debug!("verified_recevier {:?}", verified_receiver);
|
let mms = if let Some(timeout) = timeout {
|
||||||
|
verified_receiver.recv_timeout(timeout)?
|
||||||
|
} else {
|
||||||
|
verified_receiver.recv()?
|
||||||
|
};
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let mut reqs_len = 0;
|
let mut reqs_len = 0;
|
||||||
let mms_len = mms.len();
|
let mms_len = mms.len();
|
||||||
|
@ -189,6 +189,8 @@ impl BankingStage {
|
||||||
let transactions = Self::deserialize_transactions(&msgs.read());
|
let transactions = Self::deserialize_transactions(&msgs.read());
|
||||||
reqs_len += transactions.len();
|
reqs_len += transactions.len();
|
||||||
|
|
||||||
|
debug!("transactions received {}", transactions.len());
|
||||||
|
|
||||||
let transactions: Vec<_> = transactions
|
let transactions: Vec<_> = transactions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(vers)
|
.zip(vers)
|
||||||
|
@ -200,14 +202,10 @@ impl BankingStage {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
}).collect();
|
}).collect();
|
||||||
|
debug!("verified transactions {}", transactions.len());
|
||||||
|
|
||||||
Self::process_transactions(
|
let entries = Self::process_transactions(bank, &transactions, poh_service)?;
|
||||||
bank,
|
entry_sender.send(entries)?;
|
||||||
&transactions,
|
|
||||||
hash_sender,
|
|
||||||
poh_receiver,
|
|
||||||
entry_sender,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inc_new_counter_info!(
|
inc_new_counter_info!(
|
||||||
|
@ -241,63 +239,153 @@ impl Service for BankingStage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When banking is pulled out of RequestStage, add this test back in.
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bank::Bank;
|
||||||
|
use ledger::Block;
|
||||||
|
use mint::Mint;
|
||||||
|
use packet::{to_packets, PacketRecycler};
|
||||||
|
use signature::{Keypair, KeypairUtil};
|
||||||
|
use std::thread::sleep;
|
||||||
|
use transaction::Transaction;
|
||||||
|
|
||||||
//use bank::Bank;
|
#[test]
|
||||||
//use entry::Entry;
|
fn test_banking_stage_shutdown() {
|
||||||
//use hash::Hash;
|
let bank = Bank::new(&Mint::new(2));
|
||||||
//use record_stage::RecordStage;
|
let (verified_sender, verified_receiver) = channel();
|
||||||
//use record_stage::Signal;
|
let (banking_stage, _entry_receiver) =
|
||||||
//use result::Result;
|
BankingStage::new(Arc::new(bank), verified_receiver, None);
|
||||||
//use std::sync::mpsc::{channel, Sender};
|
drop(verified_sender);
|
||||||
//use std::sync::{Arc, Mutex};
|
assert_eq!(banking_stage.join().unwrap(), ());
|
||||||
//use std::time::Duration;
|
}
|
||||||
//use transaction::Transaction;
|
|
||||||
//
|
#[test]
|
||||||
//#[cfg(test)]
|
fn test_banking_stage_tick() {
|
||||||
//mod tests {
|
let bank = Bank::new(&Mint::new(2));
|
||||||
// use bank::Bank;
|
let start_hash = bank.last_id();
|
||||||
// use mint::Mint;
|
let (verified_sender, verified_receiver) = channel();
|
||||||
// use signature::{KeyPair, KeyPairUtil};
|
let (banking_stage, entry_receiver) = BankingStage::new(
|
||||||
// use transaction::Transaction;
|
Arc::new(bank),
|
||||||
//
|
verified_receiver,
|
||||||
// #[test]
|
Some(Duration::from_millis(1)),
|
||||||
// // TODO: Move this test banking_stage. Calling process_transactions() directly
|
);
|
||||||
// // defeats the purpose of this test.
|
sleep(Duration::from_millis(50));
|
||||||
// fn test_banking_sequential_consistency() {
|
drop(verified_sender);
|
||||||
// // In this attack we'll demonstrate that a verifier can interpret the ledger
|
|
||||||
// // differently if either the server doesn't signal the ledger to add an
|
let entries: Vec<_> = entry_receiver.iter().flat_map(|x| x).collect();
|
||||||
// // Entry OR if the verifier tries to parallelize across multiple Entries.
|
assert!(entries.len() != 0);
|
||||||
// let mint = Mint::new(2);
|
assert!(entries.verify(&start_hash));
|
||||||
// let bank = Bank::new(&mint);
|
assert_eq!(banking_stage.join().unwrap(), ());
|
||||||
// let banking_stage = EventProcessor::new(bank, &mint.last_id(), None);
|
}
|
||||||
//
|
|
||||||
// // Process a batch that includes a transaction that receives two tokens.
|
#[test]
|
||||||
// let alice = KeyPair::new();
|
fn test_banking_stage_no_tick() {
|
||||||
// let tx = Transaction::new(&mint.keypair(), alice.pubkey(), 2, mint.last_id());
|
let bank = Bank::new(&Mint::new(2));
|
||||||
// let transactions = vec![tx];
|
let (verified_sender, verified_receiver) = channel();
|
||||||
// let entry0 = banking_stage.process_transactions(transactions).unwrap();
|
let (banking_stage, entry_receiver) =
|
||||||
//
|
BankingStage::new(Arc::new(bank), verified_receiver, None);
|
||||||
// // Process a second batch that spends one of those tokens.
|
sleep(Duration::from_millis(1000));
|
||||||
// let tx = Transaction::new(&alice, mint.pubkey(), 1, mint.last_id());
|
drop(verified_sender);
|
||||||
// let transactions = vec![tx];
|
|
||||||
// let entry1 = banking_stage.process_transactions(transactions).unwrap();
|
let entries: Vec<_> = entry_receiver.try_iter().map(|x| x).collect();
|
||||||
//
|
assert!(entries.len() == 0);
|
||||||
// // Collect the ledger and feed it to a new bank.
|
assert_eq!(banking_stage.join().unwrap(), ());
|
||||||
// let entries = vec![entry0, entry1];
|
}
|
||||||
//
|
|
||||||
// // Assert the user holds one token, not two. If the server only output one
|
#[test]
|
||||||
// // entry, then the second transaction will be rejected, because it drives
|
fn test_banking_stage() {
|
||||||
// // the account balance below zero before the credit is added.
|
let mint = Mint::new(2);
|
||||||
// let bank = Bank::new(&mint);
|
let bank = Bank::new(&mint);
|
||||||
// for entry in entries {
|
let start_hash = bank.last_id();
|
||||||
// assert!(
|
let (verified_sender, verified_receiver) = channel();
|
||||||
// bank
|
let (banking_stage, entry_receiver) =
|
||||||
// .process_transactions(entry.transactions)
|
BankingStage::new(Arc::new(bank), verified_receiver, None);
|
||||||
// .into_iter()
|
|
||||||
// .all(|x| x.is_ok())
|
// good tx
|
||||||
// );
|
let keypair = mint.keypair();
|
||||||
// }
|
let tx = Transaction::new(&keypair, keypair.pubkey(), 1, start_hash);
|
||||||
// assert_eq!(bank.get_balance(&alice.pubkey()), Some(1));
|
|
||||||
// }
|
// good tx, but no verify
|
||||||
//}
|
let tx_no_ver = Transaction::new(&keypair, keypair.pubkey(), 1, start_hash);
|
||||||
|
|
||||||
|
// bad tx, AccountNotFound
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let tx_anf = Transaction::new(&keypair, keypair.pubkey(), 1, start_hash);
|
||||||
|
|
||||||
|
// send 'em over
|
||||||
|
let recycler = PacketRecycler::default();
|
||||||
|
let packets = to_packets(&recycler, &[tx, tx_no_ver, tx_anf]);
|
||||||
|
|
||||||
|
// glad they all fit
|
||||||
|
assert_eq!(packets.len(), 1);
|
||||||
|
verified_sender // tx, no_ver, anf
|
||||||
|
.send(vec![(packets[0].clone(), vec![1u8, 0u8, 1u8])])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
drop(verified_sender);
|
||||||
|
|
||||||
|
let entries: Vec<_> = entry_receiver.iter().map(|x| x).collect();
|
||||||
|
assert_eq!(entries.len(), 1);
|
||||||
|
let mut last_id = start_hash;
|
||||||
|
entries.iter().for_each(|entries| {
|
||||||
|
assert_eq!(entries.len(), 1);
|
||||||
|
assert!(entries.verify(&last_id));
|
||||||
|
last_id = entries.last().unwrap().id;
|
||||||
|
});
|
||||||
|
assert_eq!(banking_stage.join().unwrap(), ());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_banking_stage_entryfication() {
|
||||||
|
// In this attack we'll demonstrate that a verifier can interpret the ledger
|
||||||
|
// differently if either the server doesn't signal the ledger to add an
|
||||||
|
// Entry OR if the verifier tries to parallelize across multiple Entries.
|
||||||
|
let mint = Mint::new(2);
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
let recycler = PacketRecycler::default();
|
||||||
|
let (verified_sender, verified_receiver) = channel();
|
||||||
|
let (banking_stage, entry_receiver) =
|
||||||
|
BankingStage::new(Arc::new(bank), verified_receiver, None);
|
||||||
|
|
||||||
|
// Process a batch that includes a transaction that receives two tokens.
|
||||||
|
let alice = Keypair::new();
|
||||||
|
let tx = Transaction::new(&mint.keypair(), alice.pubkey(), 2, mint.last_id());
|
||||||
|
|
||||||
|
let packets = to_packets(&recycler, &[tx]);
|
||||||
|
verified_sender
|
||||||
|
.send(vec![(packets[0].clone(), vec![1u8])])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Process a second batch that spends one of those tokens.
|
||||||
|
let tx = Transaction::new(&alice, mint.pubkey(), 1, mint.last_id());
|
||||||
|
let packets = to_packets(&recycler, &[tx]);
|
||||||
|
verified_sender
|
||||||
|
.send(vec![(packets[0].clone(), vec![1u8])])
|
||||||
|
.unwrap();
|
||||||
|
drop(verified_sender);
|
||||||
|
assert_eq!(banking_stage.join().unwrap(), ());
|
||||||
|
|
||||||
|
// Collect the ledger and feed it to a new bank.
|
||||||
|
let ventries: Vec<_> = entry_receiver.iter().collect();
|
||||||
|
|
||||||
|
// same assertion as below, really...
|
||||||
|
assert_eq!(ventries.len(), 2);
|
||||||
|
|
||||||
|
// Assert the user holds one token, not two. If the stage only outputs one
|
||||||
|
// entry, then the second transaction will be rejected, because it drives
|
||||||
|
// the account balance below zero before the credit is added.
|
||||||
|
let bank = Bank::new(&mint);
|
||||||
|
for entries in ventries {
|
||||||
|
for entry in entries {
|
||||||
|
assert!(
|
||||||
|
bank.process_transactions(&entry.transactions)
|
||||||
|
.into_iter()
|
||||||
|
.all(|x| x.is_ok())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(bank.get_balance(&alice.pubkey()), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -200,14 +200,18 @@ fn next_hash(start_hash: &Hash, num_hashes: u64, transactions: &[Transaction]) -
|
||||||
return *start_hash;
|
return *start_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut poh = Poh::new(*start_hash, None);
|
let mut poh = Poh::new(*start_hash);
|
||||||
|
|
||||||
for _ in 1..num_hashes {
|
for _ in 1..num_hashes {
|
||||||
poh.hash();
|
poh.hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if transactions.is_empty() {
|
||||||
|
poh.tick().id
|
||||||
|
} else {
|
||||||
poh.record(Transaction::hash(transactions)).id
|
poh.record(Transaction::hash(transactions)).id
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
|
/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
|
||||||
pub fn next_entry(start_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
|
pub fn next_entry(start_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
|
||||||
|
|
33
src/poh.rs
33
src/poh.rs
|
@ -1,14 +1,10 @@
|
||||||
//! The `Poh` module provides an object for generating a Proof of History.
|
//! The `Poh` module provides an object for generating a Proof of History.
|
||||||
//! It records Hashes items on behalf of its users.
|
//! It records Hashes items on behalf of its users.
|
||||||
|
|
||||||
use hash::{hash, hashv, Hash};
|
use hash::{hash, hashv, Hash};
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
pub struct Poh {
|
pub struct Poh {
|
||||||
last_hash: Hash,
|
last_hash: Hash,
|
||||||
num_hashes: u64,
|
num_hashes: u64,
|
||||||
last_tick: Instant,
|
|
||||||
tick_duration: Option<Duration>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -19,13 +15,10 @@ pub struct PohEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Poh {
|
impl Poh {
|
||||||
pub fn new(last_hash: Hash, tick_duration: Option<Duration>) -> Self {
|
pub fn new(last_hash: Hash) -> Self {
|
||||||
let last_tick = Instant::now();
|
|
||||||
Poh {
|
Poh {
|
||||||
last_hash,
|
last_hash,
|
||||||
num_hashes: 0,
|
num_hashes: 0,
|
||||||
last_tick,
|
|
||||||
tick_duration,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +29,10 @@ impl Poh {
|
||||||
|
|
||||||
pub fn record(&mut self, mixin: Hash) -> PohEntry {
|
pub fn record(&mut self, mixin: Hash) -> PohEntry {
|
||||||
let num_hashes = self.num_hashes + 1;
|
let num_hashes = self.num_hashes + 1;
|
||||||
self.num_hashes = 0;
|
|
||||||
|
|
||||||
self.last_hash = hashv(&[&self.last_hash.as_ref(), &mixin.as_ref()]);
|
self.last_hash = hashv(&[&self.last_hash.as_ref(), &mixin.as_ref()]);
|
||||||
|
|
||||||
|
self.num_hashes = 0;
|
||||||
|
|
||||||
PohEntry {
|
PohEntry {
|
||||||
num_hashes,
|
num_hashes,
|
||||||
id: self.last_hash,
|
id: self.last_hash,
|
||||||
|
@ -49,23 +42,21 @@ impl Poh {
|
||||||
|
|
||||||
// emissions of Ticks (i.e. PohEntries without a mixin) allows
|
// emissions of Ticks (i.e. PohEntries without a mixin) allows
|
||||||
// validators to parallelize the work of catching up
|
// validators to parallelize the work of catching up
|
||||||
pub fn tick(&mut self) -> Option<PohEntry> {
|
pub fn tick(&mut self) -> PohEntry {
|
||||||
if let Some(tick_duration) = self.tick_duration {
|
self.hash();
|
||||||
if self.last_tick.elapsed() >= tick_duration {
|
|
||||||
self.last_tick = Instant::now();
|
let num_hashes = self.num_hashes;
|
||||||
let entry = PohEntry {
|
self.num_hashes = 0;
|
||||||
num_hashes: self.num_hashes,
|
|
||||||
|
PohEntry {
|
||||||
|
num_hashes,
|
||||||
id: self.last_hash,
|
id: self.last_hash,
|
||||||
mixin: None,
|
mixin: None,
|
||||||
};
|
|
||||||
self.num_hashes = 0;
|
|
||||||
return Some(entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
pub fn verify(initial: Hash, entries: &[PohEntry]) -> bool {
|
pub fn verify(initial: Hash, entries: &[PohEntry]) -> bool {
|
||||||
let mut last_hash = initial;
|
let mut last_hash = initial;
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,14 @@
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use poh::{Poh, PohEntry};
|
use poh::{Poh, PohEntry};
|
||||||
use service::Service;
|
use service::Service;
|
||||||
use std::sync::mpsc::{channel, Receiver, RecvError, Sender, TryRecvError};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread::{self, Builder, JoinHandle};
|
use std::thread::{self, Builder, JoinHandle};
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
pub struct PohService {
|
pub struct PohService {
|
||||||
|
poh: Arc<Mutex<Poh>>,
|
||||||
thread_hdl: JoinHandle<()>,
|
thread_hdl: JoinHandle<()>,
|
||||||
|
run_poh: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PohService {
|
impl PohService {
|
||||||
|
@ -24,74 +26,83 @@ impl PohService {
|
||||||
/// sending back Entry messages until either the receiver or sender channel is closed.
|
/// sending back Entry messages until either the receiver or sender channel is closed.
|
||||||
/// if tick_duration is some, service will automatically produce entries every
|
/// if tick_duration is some, service will automatically produce entries every
|
||||||
/// `tick_duration`.
|
/// `tick_duration`.
|
||||||
pub fn new(
|
pub fn new(start_hash: Hash, run_poh: bool) -> Self {
|
||||||
start_hash: Hash,
|
let poh = Arc::new(Mutex::new(Poh::new(start_hash)));
|
||||||
hash_receiver: Receiver<Hash>,
|
let run_poh = Arc::new(AtomicBool::new(run_poh));
|
||||||
tick_duration: Option<Duration>,
|
|
||||||
) -> (Self, Receiver<PohEntry>) {
|
|
||||||
let (poh_sender, poh_receiver) = channel();
|
|
||||||
|
|
||||||
|
let thread_poh = poh.clone();
|
||||||
|
let thread_run_poh = run_poh.clone();
|
||||||
let thread_hdl = Builder::new()
|
let thread_hdl = Builder::new()
|
||||||
.name("solana-record-service".to_string())
|
.name("solana-record-service".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let mut poh = Poh::new(start_hash, tick_duration);
|
while thread_run_poh.load(Ordering::Relaxed) {
|
||||||
if tick_duration.is_some() {
|
thread_poh.lock().unwrap().hash();
|
||||||
loop {
|
|
||||||
if Self::try_process_hashes(&mut poh, &hash_receiver, &poh_sender).is_err()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
poh.hash();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = Self::process_hashes(&mut poh, &hash_receiver, &poh_sender);
|
|
||||||
}
|
}
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
(PohService { thread_hdl }, poh_receiver)
|
PohService {
|
||||||
}
|
poh,
|
||||||
|
run_poh,
|
||||||
fn process_hash(hash: Hash, poh: &mut Poh, sender: &Sender<PohEntry>) -> Result<(), ()> {
|
thread_hdl,
|
||||||
let resp = poh.record(hash);
|
|
||||||
sender.send(resp).or(Err(()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_hashes(
|
|
||||||
poh: &mut Poh,
|
|
||||||
receiver: &Receiver<Hash>,
|
|
||||||
sender: &Sender<PohEntry>,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
loop {
|
|
||||||
match receiver.recv() {
|
|
||||||
Ok(hash) => Self::process_hash(hash, poh, sender)?,
|
|
||||||
Err(RecvError) => return Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_process_hashes(
|
pub fn tick(&self) -> PohEntry {
|
||||||
poh: &mut Poh,
|
let mut poh = self.poh.lock().unwrap();
|
||||||
receiver: &Receiver<Hash>,
|
poh.tick()
|
||||||
sender: &Sender<PohEntry>,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
loop {
|
|
||||||
if let Some(resp) = poh.tick() {
|
|
||||||
sender.send(resp).or(Err(()))?;
|
|
||||||
}
|
|
||||||
match receiver.try_recv() {
|
|
||||||
Ok(hash) => Self::process_hash(hash, poh, sender)?,
|
|
||||||
Err(TryRecvError::Empty) => return Ok(()),
|
|
||||||
Err(TryRecvError::Disconnected) => return Err(()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record(&self, mixin: Hash) -> PohEntry {
|
||||||
|
let mut poh = self.poh.lock().unwrap();
|
||||||
|
poh.record(mixin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn process_hash(
|
||||||
|
// mixin: Option<Hash>,
|
||||||
|
// poh: &mut Poh,
|
||||||
|
// sender: &Sender<PohEntry>,
|
||||||
|
// ) -> Result<(), ()> {
|
||||||
|
// let resp = match mixin {
|
||||||
|
// Some(mixin) => poh.record(mixin),
|
||||||
|
// None => poh.tick(),
|
||||||
|
// };
|
||||||
|
// sender.send(resp).or(Err(()))?;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn process_hashes(
|
||||||
|
// poh: &mut Poh,
|
||||||
|
// receiver: &Receiver<Option<Hash>>,
|
||||||
|
// sender: &Sender<PohEntry>,
|
||||||
|
// ) -> Result<(), ()> {
|
||||||
|
// loop {
|
||||||
|
// match receiver.recv() {
|
||||||
|
// Ok(hash) => Self::process_hash(hash, poh, sender)?,
|
||||||
|
// Err(RecvError) => return Err(()),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn try_process_hashes(
|
||||||
|
// poh: &mut Poh,
|
||||||
|
// receiver: &Receiver<Option<Hash>>,
|
||||||
|
// sender: &Sender<PohEntry>,
|
||||||
|
// ) -> Result<(), ()> {
|
||||||
|
// loop {
|
||||||
|
// match receiver.try_recv() {
|
||||||
|
// Ok(hash) => Self::process_hash(hash, poh, sender)?,
|
||||||
|
// Err(TryRecvError::Empty) => return Ok(()),
|
||||||
|
// Err(TryRecvError::Disconnected) => return Err(()),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service for PohService {
|
impl Service for PohService {
|
||||||
type JoinReturnType = ();
|
type JoinReturnType = ();
|
||||||
|
|
||||||
fn join(self) -> thread::Result<()> {
|
fn join(self) -> thread::Result<()> {
|
||||||
|
self.run_poh.store(false, Ordering::Relaxed);
|
||||||
self.thread_hdl.join()
|
self.thread_hdl.join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,57 +111,38 @@ impl Service for PohService {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use poh::verify;
|
use poh::verify;
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_poh() {
|
fn test_poh() {
|
||||||
let (hash_sender, hash_receiver) = channel();
|
let poh_service = PohService::new(Hash::default(), false);
|
||||||
let (poh_service, poh_receiver) = PohService::new(Hash::default(), hash_receiver, None);
|
|
||||||
|
|
||||||
hash_sender.send(Hash::default()).unwrap();
|
let entry0 = poh_service.record(Hash::default());
|
||||||
sleep(Duration::from_millis(1));
|
sleep(Duration::from_millis(1));
|
||||||
hash_sender.send(Hash::default()).unwrap();
|
let entry1 = poh_service.record(Hash::default());
|
||||||
sleep(Duration::from_millis(1));
|
sleep(Duration::from_millis(1));
|
||||||
hash_sender.send(Hash::default()).unwrap();
|
let entry2 = poh_service.record(Hash::default());
|
||||||
|
|
||||||
let entry0 = poh_receiver.recv().unwrap();
|
|
||||||
let entry1 = poh_receiver.recv().unwrap();
|
|
||||||
let entry2 = poh_receiver.recv().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(entry0.num_hashes, 1);
|
assert_eq!(entry0.num_hashes, 1);
|
||||||
assert_eq!(entry0.num_hashes, 1);
|
assert_eq!(entry1.num_hashes, 1);
|
||||||
assert_eq!(entry0.num_hashes, 1);
|
assert_eq!(entry2.num_hashes, 1);
|
||||||
|
|
||||||
drop(hash_sender);
|
assert_eq!(poh_service.join().unwrap(), ());
|
||||||
assert_eq!(poh_service.thread_hdl.join().unwrap(), ());
|
|
||||||
|
|
||||||
assert!(verify(Hash::default(), &[entry0, entry1, entry2]));
|
assert!(verify(Hash::default(), &[entry0, entry1, entry2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_poh_closed_sender() {
|
fn test_do_poh() {
|
||||||
let (hash_sender, hash_receiver) = channel();
|
let poh_service = PohService::new(Hash::default(), true);
|
||||||
let (poh_service, poh_receiver) = PohService::new(Hash::default(), hash_receiver, None);
|
|
||||||
drop(poh_receiver);
|
|
||||||
hash_sender.send(Hash::default()).unwrap();
|
|
||||||
assert_eq!(poh_service.thread_hdl.join().unwrap(), ());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_poh_clock() {
|
|
||||||
let (hash_sender, hash_receiver) = channel();
|
|
||||||
let (_poh_service, poh_receiver) = PohService::new(
|
|
||||||
Hash::default(),
|
|
||||||
hash_receiver,
|
|
||||||
Some(Duration::from_millis(1)),
|
|
||||||
);
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(50));
|
sleep(Duration::from_millis(50));
|
||||||
drop(hash_sender);
|
let entry = poh_service.tick();
|
||||||
let pohs: Vec<_> = poh_receiver.iter().map(|x| x).collect();
|
assert!(entry.num_hashes > 1);
|
||||||
assert!(pohs.len() > 1);
|
|
||||||
|
|
||||||
assert!(verify(Hash::default(), &pohs));
|
assert_eq!(poh_service.join().unwrap(), ());
|
||||||
|
|
||||||
|
assert!(verify(Hash::default(), &vec![entry]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue