solana/src/poh_recorder.rs

252 lines
7.9 KiB
Rust
Raw Normal View History

//! The `poh_recorder` module provides an object for synchronizing with Proof of History.
//! It synchronizes PoH, bank's register_tick and the ledger
//!
2018-12-07 19:16:27 -08:00
use crate::entry::Entry;
use crate::poh::Poh;
use crate::result::{Error, Result};
use solana_runtime::bank::Bank;
2018-11-16 08:04:46 -08:00
use solana_sdk::hash::Hash;
2018-11-29 16:18:47 -08:00
use solana_sdk::transaction::Transaction;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PohRecorderError {
InvalidCallingObject,
MaxHeightReached,
MinHeightNotReached,
}
#[derive(Clone)]
pub struct WorkingBank {
pub bank: Arc<Bank>,
pub sender: Sender<Vec<Entry>>,
pub min_tick_height: u64,
pub max_tick_height: u64,
}
#[derive(Clone)]
pub struct PohRecorder {
poh: Arc<Mutex<Poh>>,
tick_cache: Arc<Mutex<Vec<Entry>>>,
}
impl PohRecorder {
pub fn hash(&self) {
// TODO: amortize the cost of this lock by doing the loop in here for
// some min amount of hashes
let mut poh = self.poh.lock().unwrap();
2018-12-12 18:52:11 -08:00
poh.hash();
}
2018-12-12 18:52:11 -08:00
fn flush_cache(&self, working_bank: &WorkingBank) -> Result<()> {
let mut cache = vec![];
std::mem::swap(&mut cache, &mut self.tick_cache.lock().unwrap());
if !cache.is_empty() {
for t in &cache {
working_bank.bank.register_tick(&t.id);
}
working_bank.sender.send(cache)?;
}
2018-12-12 18:52:11 -08:00
Ok(())
}
pub fn tick(&self, working_bank: &WorkingBank) -> Result<()> {
// Register and send the entry out while holding the lock if the max PoH height
// hasn't been reached.
// This guarantees PoH order and Entry production and banks LastId queue is the same
let mut poh = self.poh.lock().unwrap();
2018-12-12 18:52:11 -08:00
Self::check_tick_height(&poh, working_bank).map_err(|e| {
let tick = Self::generate_tick(&mut poh);
self.tick_cache.lock().unwrap().push(tick);
e
})?;
;
self.flush_cache(working_bank)?;
2018-12-12 18:52:11 -08:00
Self::register_and_send_tick(&mut *poh, working_bank)
}
pub fn record(
&self,
mixin: Hash,
txs: Vec<Transaction>,
working_bank: &WorkingBank,
) -> Result<()> {
// Register and send the entry out while holding the lock.
// This guarantees PoH order and Entry production and banks LastId queue is the same.
let mut poh = self.poh.lock().unwrap();
2018-12-12 18:52:11 -08:00
Self::check_tick_height(&poh, working_bank)?;
self.flush_cache(working_bank)?;
2018-12-12 18:52:11 -08:00
Self::record_and_send_txs(&mut *poh, mixin, txs, working_bank)
}
/// A recorder to synchronize PoH with the following data structures
/// * bank - the LastId's queue is updated on `tick` and `record` events
/// * sender - the Entry channel that outputs to the ledger
pub fn new(tick_height: u64, last_entry_id: Hash) -> Self {
let poh = Arc::new(Mutex::new(Poh::new(last_entry_id, tick_height)));
let tick_cache = Arc::new(Mutex::new(vec![]));
PohRecorder { poh, tick_cache }
}
fn check_tick_height(poh: &Poh, working_bank: &WorkingBank) -> Result<()> {
if poh.tick_height < working_bank.min_tick_height {
Err(Error::PohRecorderError(
PohRecorderError::MinHeightNotReached,
))
} else if poh.tick_height >= working_bank.max_tick_height {
Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached))
} else {
Ok(())
}
}
fn record_and_send_txs(
poh: &mut Poh,
mixin: Hash,
txs: Vec<Transaction>,
working_bank: &WorkingBank,
) -> Result<()> {
let entry = poh.record(mixin);
assert!(!txs.is_empty(), "Entries without transactions are used to track real-time passing in the ledger and can only be generated with PohRecorder::tick function");
let entry = Entry {
num_hashes: entry.num_hashes,
id: entry.id,
transactions: txs,
};
working_bank.sender.send(vec![entry])?;
Ok(())
}
fn generate_tick(poh: &mut Poh) -> Entry {
let tick = poh.tick();
Entry {
num_hashes: tick.num_hashes,
id: tick.id,
transactions: vec![],
}
}
fn register_and_send_tick(poh: &mut Poh, working_bank: &WorkingBank) -> Result<()> {
let tick = Self::generate_tick(poh);
working_bank.bank.register_tick(&tick.id);
working_bank.sender.send(vec![tick])?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
2018-12-07 19:16:27 -08:00
use crate::test_tx::test_tx;
use solana_sdk::genesis_block::GenesisBlock;
2018-11-16 08:04:46 -08:00
use solana_sdk::hash::hash;
use std::sync::mpsc::channel;
use std::sync::Arc;
#[test]
fn test_poh_recorder() {
let (genesis_block, _mint_keypair) = GenesisBlock::new(2);
2019-01-24 12:04:04 -08:00
let bank = Arc::new(Bank::new(&genesis_block));
let prev_id = bank.last_id();
let (entry_sender, entry_receiver) = channel();
let poh_recorder = PohRecorder::new(0, prev_id);
let working_bank = WorkingBank {
bank,
sender: entry_sender,
min_tick_height: 0,
max_tick_height: 2,
};
//send some data
let h1 = hash(b"hello world!");
let tx = test_tx();
poh_recorder
.record(h1, vec![tx.clone()], &working_bank)
.unwrap();
//get some events
2019-02-19 22:18:57 -08:00
let _e = entry_receiver.recv().unwrap();
poh_recorder.tick(&working_bank).unwrap();
2019-02-19 22:18:57 -08:00
let _e = entry_receiver.recv().unwrap();
poh_recorder.tick(&working_bank).unwrap();
2019-02-19 22:18:57 -08:00
let _e = entry_receiver.recv().unwrap();
2018-12-12 18:52:11 -08:00
// max tick height reached
assert!(poh_recorder.tick(&working_bank).is_err());
assert!(poh_recorder.record(h1, vec![tx], &working_bank).is_err());
2018-12-12 18:52:11 -08:00
//make sure it handles channel close correctly
drop(entry_receiver);
assert!(poh_recorder.tick(&working_bank).is_err());
}
#[test]
fn test_poh_recorder_tick_cache() {
let (genesis_block, _mint_keypair) = GenesisBlock::new(2);
let bank = Arc::new(Bank::new(&genesis_block));
let prev_id = bank.last_id();
let (entry_sender, entry_receiver) = channel();
let poh_recorder = PohRecorder::new(0, prev_id);
let working_bank = WorkingBank {
bank,
sender: entry_sender,
min_tick_height: 1,
max_tick_height: 2,
};
// tick should be cached
assert!(poh_recorder.tick(&working_bank).is_err());
assert!(entry_receiver.try_recv().is_err());
// working_bank should be at the right height
poh_recorder.tick(&working_bank).unwrap();
let entries = entry_receiver.recv().unwrap();
assert_eq!(entries.len(), 1);
let entries = entry_receiver.recv().unwrap();
assert_eq!(entries.len(), 1);
}
#[test]
fn test_poh_recorder_tick_cache_old_working_bank() {
let (genesis_block, _mint_keypair) = GenesisBlock::new(2);
let bank = Arc::new(Bank::new(&genesis_block));
let prev_id = bank.last_id();
let (entry_sender, entry_receiver) = channel();
let poh_recorder = PohRecorder::new(0, prev_id);
let working_bank = WorkingBank {
bank,
sender: entry_sender,
min_tick_height: 1,
max_tick_height: 1,
};
// tick should be cached
assert_matches!(
poh_recorder.tick(&working_bank),
Err(Error::PohRecorderError(
PohRecorderError::MinHeightNotReached
))
);
// working_bank should be past MaxHeight
assert_matches!(
poh_recorder.tick(&working_bank),
Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached))
);
assert_eq!(poh_recorder.tick_cache.lock().unwrap().len(), 2);
assert!(entry_receiver.try_recv().is_err());
}
}