2018-10-25 14:56:21 -07:00
|
|
|
//! The `poh_service` module implements a service that records the passing of
|
|
|
|
//! "ticks", a measure of time in the PoH stream
|
2019-02-24 08:59:49 -08:00
|
|
|
use crate::poh_recorder::PohRecorder;
|
2018-12-07 20:16:27 -07:00
|
|
|
use crate::service::Service;
|
2019-05-22 14:21:43 -07:00
|
|
|
use core_affinity;
|
2019-05-18 14:01:36 -07:00
|
|
|
use solana_sdk::poh_config::PohConfig;
|
2018-10-25 14:56:21 -07:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2019-02-24 08:59:49 -08:00
|
|
|
use std::sync::{Arc, Mutex};
|
2019-02-18 20:28:10 -07:00
|
|
|
use std::thread::{self, sleep, Builder, JoinHandle};
|
2019-04-29 15:26:52 -07:00
|
|
|
|
2018-10-25 14:56:21 -07:00
|
|
|
pub struct PohService {
|
2019-02-24 08:59:49 -08:00
|
|
|
tick_producer: JoinHandle<()>,
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
|
2019-05-18 14:01:36 -07:00
|
|
|
// Number of hashes to batch together.
|
|
|
|
// * If this number is too small, PoH hash rate will suffer.
|
|
|
|
// * The larger this number is from 1, the speed of recording transactions will suffer due to lock
|
|
|
|
// contention with the PoH hashing within `tick_producer()`.
|
|
|
|
//
|
|
|
|
// See benches/poh.rs for some benchmarks that attempt to justify this magic number.
|
2019-05-22 15:54:24 -07:00
|
|
|
pub const NUM_HASHES_PER_BATCH: u64 = 1;
|
2019-05-18 14:01:36 -07:00
|
|
|
|
2018-10-25 14:56:21 -07:00
|
|
|
impl PohService {
|
2019-02-17 20:15:34 -07:00
|
|
|
pub fn new(
|
2019-02-24 08:59:49 -08:00
|
|
|
poh_recorder: Arc<Mutex<PohRecorder>>,
|
2019-05-18 14:01:36 -07:00
|
|
|
poh_config: &Arc<PohConfig>,
|
2019-03-04 16:33:14 -08:00
|
|
|
poh_exit: &Arc<AtomicBool>,
|
2019-02-24 08:59:49 -08:00
|
|
|
) -> Self {
|
2018-10-25 14:56:21 -07:00
|
|
|
let poh_exit_ = poh_exit.clone();
|
2019-05-18 14:01:36 -07:00
|
|
|
let poh_config = poh_config.clone();
|
2018-10-25 14:56:21 -07:00
|
|
|
let tick_producer = Builder::new()
|
|
|
|
.name("solana-poh-service-tick_producer".to_string())
|
|
|
|
.spawn(move || {
|
2019-05-18 14:01:36 -07:00
|
|
|
if poh_config.hashes_per_tick.is_none() {
|
|
|
|
Self::sleepy_tick_producer(poh_recorder, &poh_config, &poh_exit_);
|
|
|
|
} else {
|
2019-05-22 15:54:24 -07:00
|
|
|
// PoH service runs in a tight loop, generating hashes as fast as possible.
|
|
|
|
// Let's dedicate one of the CPU cores to this thread so that it can gain
|
|
|
|
// from cache performance.
|
2019-05-22 14:21:43 -07:00
|
|
|
if let Some(cores) = core_affinity::get_core_ids() {
|
|
|
|
core_affinity::set_for_current(cores[0]);
|
|
|
|
}
|
2019-05-18 14:01:36 -07:00
|
|
|
Self::tick_producer(poh_recorder, &poh_exit_);
|
|
|
|
}
|
2018-10-25 14:56:21 -07:00
|
|
|
poh_exit_.store(true, Ordering::Relaxed);
|
2018-12-07 20:01:28 -07:00
|
|
|
})
|
|
|
|
.unwrap();
|
2018-10-25 14:56:21 -07:00
|
|
|
|
2019-03-04 20:50:02 -08:00
|
|
|
Self { tick_producer }
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
|
2019-05-18 14:01:36 -07:00
|
|
|
fn sleepy_tick_producer(
|
|
|
|
poh_recorder: Arc<Mutex<PohRecorder>>,
|
|
|
|
poh_config: &PohConfig,
|
2019-01-26 13:58:08 +05:30
|
|
|
poh_exit: &AtomicBool,
|
2019-02-24 08:59:49 -08:00
|
|
|
) {
|
2019-05-18 14:01:36 -07:00
|
|
|
while !poh_exit.load(Ordering::Relaxed) {
|
|
|
|
sleep(poh_config.target_tick_duration);
|
|
|
|
poh_recorder.lock().unwrap().tick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick_producer(poh_recorder: Arc<Mutex<PohRecorder>>, poh_exit: &AtomicBool) {
|
|
|
|
let poh = poh_recorder.lock().unwrap().poh.clone();
|
2018-10-25 14:56:21 -07:00
|
|
|
loop {
|
2019-05-18 14:01:36 -07:00
|
|
|
if poh.lock().unwrap().hash(NUM_HASHES_PER_BATCH) {
|
|
|
|
// Lock PohRecorder only for the final hash...
|
|
|
|
poh_recorder.lock().unwrap().tick();
|
|
|
|
if poh_exit.load(Ordering::Relaxed) {
|
|
|
|
break;
|
2019-02-26 10:48:18 -08:00
|
|
|
}
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Service for PohService {
|
2019-02-24 08:59:49 -08:00
|
|
|
type JoinReturnType = ();
|
2018-10-25 14:56:21 -07:00
|
|
|
|
2019-02-24 08:59:49 -08:00
|
|
|
fn join(self) -> thread::Result<()> {
|
2018-10-25 14:56:21 -07:00
|
|
|
self.tick_producer.join()
|
|
|
|
}
|
|
|
|
}
|
2018-12-05 12:49:41 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2019-02-09 09:25:11 -08:00
|
|
|
use super::*;
|
2019-03-29 20:00:36 -07:00
|
|
|
use crate::blocktree::{get_tmp_ledger_path, Blocktree};
|
2019-05-22 20:39:00 -07:00
|
|
|
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
|
2019-04-19 02:39:44 -07:00
|
|
|
use crate::leader_schedule_cache::LeaderScheduleCache;
|
2019-02-24 08:59:49 -08:00
|
|
|
use crate::poh_recorder::WorkingBank;
|
|
|
|
use crate::result::Result;
|
2018-12-07 20:16:27 -07:00
|
|
|
use crate::test_tx::test_tx;
|
2019-02-19 16:17:36 -08:00
|
|
|
use solana_runtime::bank::Bank;
|
2018-12-05 12:49:41 -08:00
|
|
|
use solana_sdk::hash::hash;
|
2019-03-20 13:49:46 -07:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2019-05-18 14:01:36 -07:00
|
|
|
use std::time::Duration;
|
2018-12-05 12:49:41 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_poh_service() {
|
2019-05-22 20:39:00 -07:00
|
|
|
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(2);
|
2019-01-24 12:04:04 -08:00
|
|
|
let bank = Arc::new(Bank::new(&genesis_block));
|
2019-03-02 10:25:16 -08:00
|
|
|
let prev_hash = bank.last_blockhash();
|
2019-03-29 20:00:36 -07:00
|
|
|
let ledger_path = get_tmp_ledger_path!();
|
|
|
|
{
|
|
|
|
let blocktree =
|
|
|
|
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
2019-05-18 14:01:36 -07:00
|
|
|
let poh_config = Arc::new(PohConfig {
|
|
|
|
hashes_per_tick: Some(2),
|
|
|
|
target_tick_duration: Duration::from_millis(42),
|
|
|
|
});
|
2019-03-29 20:00:36 -07:00
|
|
|
let (poh_recorder, entry_receiver) = PohRecorder::new(
|
|
|
|
bank.tick_height(),
|
|
|
|
prev_hash,
|
|
|
|
bank.slot(),
|
|
|
|
Some(4),
|
|
|
|
bank.ticks_per_slot(),
|
|
|
|
&Pubkey::default(),
|
|
|
|
&Arc::new(blocktree),
|
2019-04-19 02:39:44 -07:00
|
|
|
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
|
2019-05-18 14:01:36 -07:00
|
|
|
&poh_config,
|
2019-03-29 20:00:36 -07:00
|
|
|
);
|
|
|
|
let poh_recorder = Arc::new(Mutex::new(poh_recorder));
|
|
|
|
let exit = Arc::new(AtomicBool::new(false));
|
|
|
|
let working_bank = WorkingBank {
|
|
|
|
bank: bank.clone(),
|
|
|
|
min_tick_height: bank.tick_height(),
|
|
|
|
max_tick_height: std::u64::MAX,
|
|
|
|
};
|
|
|
|
|
|
|
|
let entry_producer: JoinHandle<Result<()>> = {
|
|
|
|
let poh_recorder = poh_recorder.clone();
|
|
|
|
let exit = exit.clone();
|
|
|
|
|
|
|
|
Builder::new()
|
|
|
|
.name("solana-poh-service-entry_producer".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
loop {
|
|
|
|
// send some data
|
|
|
|
let h1 = hash(b"hello world!");
|
|
|
|
let tx = test_tx();
|
2019-05-18 14:01:36 -07:00
|
|
|
let _ = poh_recorder
|
2019-03-29 20:00:36 -07:00
|
|
|
.lock()
|
|
|
|
.unwrap()
|
2019-05-18 14:01:36 -07:00
|
|
|
.record(bank.slot(), h1, vec![tx]);
|
2019-03-29 20:00:36 -07:00
|
|
|
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
break Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap()
|
|
|
|
};
|
|
|
|
|
2019-05-18 14:01:36 -07:00
|
|
|
let poh_service = PohService::new(poh_recorder.clone(), &poh_config, &exit);
|
2019-03-29 20:00:36 -07:00
|
|
|
poh_recorder.lock().unwrap().set_working_bank(working_bank);
|
|
|
|
|
|
|
|
// get some events
|
|
|
|
let mut hashes = 0;
|
|
|
|
let mut need_tick = true;
|
|
|
|
let mut need_entry = true;
|
|
|
|
let mut need_partial = true;
|
|
|
|
|
|
|
|
while need_tick || need_entry || need_partial {
|
|
|
|
for entry in entry_receiver.recv().unwrap().1 {
|
|
|
|
let entry = &entry.0;
|
|
|
|
if entry.is_tick() {
|
2019-05-18 14:01:36 -07:00
|
|
|
assert!(
|
|
|
|
entry.num_hashes <= poh_config.hashes_per_tick.unwrap(),
|
|
|
|
format!(
|
|
|
|
"{} <= {}",
|
|
|
|
entry.num_hashes,
|
|
|
|
poh_config.hashes_per_tick.unwrap()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if entry.num_hashes == poh_config.hashes_per_tick.unwrap() {
|
2019-03-29 20:00:36 -07:00
|
|
|
need_tick = false;
|
|
|
|
} else {
|
|
|
|
need_partial = false;
|
2018-12-05 12:49:41 -08:00
|
|
|
}
|
|
|
|
|
2019-03-29 20:00:36 -07:00
|
|
|
hashes += entry.num_hashes;
|
2018-12-05 12:49:41 -08:00
|
|
|
|
2019-05-18 14:01:36 -07:00
|
|
|
assert_eq!(hashes, poh_config.hashes_per_tick.unwrap());
|
2018-12-05 12:49:41 -08:00
|
|
|
|
2019-03-29 20:00:36 -07:00
|
|
|
hashes = 0;
|
|
|
|
} else {
|
|
|
|
assert!(entry.num_hashes >= 1);
|
|
|
|
need_entry = false;
|
2019-05-18 14:01:36 -07:00
|
|
|
hashes += entry.num_hashes;
|
2019-03-29 20:00:36 -07:00
|
|
|
}
|
2018-12-05 12:49:41 -08:00
|
|
|
}
|
|
|
|
}
|
2019-03-29 20:00:36 -07:00
|
|
|
exit.store(true, Ordering::Relaxed);
|
|
|
|
let _ = poh_service.join().unwrap();
|
|
|
|
let _ = entry_producer.join().unwrap();
|
2018-12-05 12:49:41 -08:00
|
|
|
}
|
2019-03-29 20:00:36 -07:00
|
|
|
Blocktree::destroy(&ledger_path).unwrap();
|
2018-12-05 12:49:41 -08:00
|
|
|
}
|
|
|
|
}
|