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-18 06:33:07 -08:00
|
|
|
use crate::bank::Bank;
|
2019-02-15 22:10:21 -08:00
|
|
|
use crate::poh_recorder::PohRecorder;
|
2018-12-07 20:16:27 -07:00
|
|
|
use crate::result::Result;
|
|
|
|
use crate::service::Service;
|
2018-10-25 14:56:21 -07:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::thread::sleep;
|
|
|
|
use std::thread::{self, Builder, JoinHandle};
|
|
|
|
use std::time::Duration;
|
2019-02-06 21:09:46 -08:00
|
|
|
|
2018-10-25 14:56:21 -07:00
|
|
|
pub const NUM_TICKS_PER_SECOND: usize = 10;
|
|
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
2019-02-09 09:25:11 -08:00
|
|
|
pub enum PohServiceConfig {
|
2019-02-06 21:09:46 -08:00
|
|
|
/// * `Tick` - Run full PoH thread. Tick is a rough estimate of how many hashes to roll before
|
|
|
|
/// transmitting a new entry.
|
2018-10-25 14:56:21 -07:00
|
|
|
Tick(usize),
|
2019-02-06 21:09:46 -08:00
|
|
|
/// * `Sleep`- Low power mode. Sleep is a rough estimate of how long to sleep before rolling 1
|
|
|
|
/// PoH once and producing 1 tick.
|
2018-10-25 14:56:21 -07:00
|
|
|
Sleep(Duration),
|
|
|
|
}
|
|
|
|
|
2019-02-09 09:25:11 -08:00
|
|
|
impl Default for PohServiceConfig {
|
|
|
|
fn default() -> PohServiceConfig {
|
2018-10-25 14:56:21 -07:00
|
|
|
// TODO: Change this to Tick to enable PoH
|
2019-02-09 09:25:11 -08:00
|
|
|
PohServiceConfig::Sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND as u64))
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PohService {
|
|
|
|
tick_producer: JoinHandle<Result<()>>,
|
2019-02-17 20:15:34 -07:00
|
|
|
poh_exit: Arc<AtomicBool>,
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PohService {
|
2018-12-07 20:01:28 -07:00
|
|
|
pub fn exit(&self) {
|
2018-10-25 14:56:21 -07:00
|
|
|
self.poh_exit.store(true, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn close(self) -> thread::Result<Result<()>> {
|
|
|
|
self.exit();
|
|
|
|
self.join()
|
|
|
|
}
|
|
|
|
|
2019-02-17 20:15:34 -07:00
|
|
|
pub fn new(
|
2019-02-18 06:33:07 -08:00
|
|
|
bank: Arc<Bank>,
|
2019-02-17 20:15:34 -07:00
|
|
|
poh_recorder: PohRecorder,
|
|
|
|
config: PohServiceConfig,
|
|
|
|
poh_exit: Arc<AtomicBool>,
|
|
|
|
) -> Self {
|
2018-10-25 14:56:21 -07:00
|
|
|
// PohService is a headless producer, so when it exits it should notify the banking stage.
|
|
|
|
// Since channel are not used to talk between these threads an AtomicBool is used as a
|
|
|
|
// signal.
|
|
|
|
let poh_exit_ = poh_exit.clone();
|
|
|
|
// Single thread to generate ticks
|
|
|
|
let tick_producer = Builder::new()
|
|
|
|
.name("solana-poh-service-tick_producer".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
let mut poh_recorder_ = poh_recorder;
|
2019-02-18 06:33:07 -08:00
|
|
|
let bank = bank.clone();
|
|
|
|
let return_value =
|
|
|
|
Self::tick_producer(&bank, &mut poh_recorder_, config, &poh_exit_);
|
2018-10-25 14:56:21 -07:00
|
|
|
poh_exit_.store(true, Ordering::Relaxed);
|
|
|
|
return_value
|
2018-12-07 20:01:28 -07:00
|
|
|
})
|
|
|
|
.unwrap();
|
2018-10-25 14:56:21 -07:00
|
|
|
|
2018-12-07 20:01:28 -07:00
|
|
|
Self {
|
2018-10-25 14:56:21 -07:00
|
|
|
tick_producer,
|
|
|
|
poh_exit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 13:58:08 +05:30
|
|
|
fn tick_producer(
|
2019-02-18 06:33:07 -08:00
|
|
|
bank: &Arc<Bank>,
|
2019-01-26 13:58:08 +05:30
|
|
|
poh: &mut PohRecorder,
|
2019-02-09 09:25:11 -08:00
|
|
|
config: PohServiceConfig,
|
2019-01-26 13:58:08 +05:30
|
|
|
poh_exit: &AtomicBool,
|
|
|
|
) -> Result<()> {
|
2018-10-25 14:56:21 -07:00
|
|
|
loop {
|
|
|
|
match config {
|
2019-02-09 09:25:11 -08:00
|
|
|
PohServiceConfig::Tick(num) => {
|
2018-12-05 12:49:41 -08:00
|
|
|
for _ in 1..num {
|
2019-02-17 20:15:34 -07:00
|
|
|
poh.hash()?;
|
2018-10-25 14:56:21 -07:00
|
|
|
}
|
|
|
|
}
|
2019-02-09 09:25:11 -08:00
|
|
|
PohServiceConfig::Sleep(duration) => {
|
2018-10-25 14:56:21 -07:00
|
|
|
sleep(duration);
|
|
|
|
}
|
|
|
|
}
|
2019-02-18 06:33:07 -08:00
|
|
|
poh.tick(&bank)?;
|
2018-10-25 14:56:21 -07:00
|
|
|
if poh_exit.load(Ordering::Relaxed) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Service for PohService {
|
|
|
|
type JoinReturnType = Result<()>;
|
|
|
|
|
|
|
|
fn join(self) -> thread::Result<Result<()>> {
|
|
|
|
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::*;
|
2018-12-07 20:16:27 -07:00
|
|
|
use crate::bank::Bank;
|
2019-01-24 12:04:04 -08:00
|
|
|
use crate::genesis_block::GenesisBlock;
|
2018-12-07 20:16:27 -07:00
|
|
|
use crate::test_tx::test_tx;
|
2018-12-05 12:49:41 -08:00
|
|
|
use solana_sdk::hash::hash;
|
|
|
|
use std::sync::mpsc::channel;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_poh_service() {
|
2019-02-05 08:03:52 -08:00
|
|
|
let (genesis_block, _mint_keypair) = GenesisBlock::new(2);
|
2019-01-24 12:04:04 -08:00
|
|
|
let bank = Arc::new(Bank::new(&genesis_block));
|
2018-12-05 12:49:41 -08:00
|
|
|
let prev_id = bank.last_id();
|
|
|
|
let (entry_sender, entry_receiver) = channel();
|
2019-02-18 06:33:07 -08:00
|
|
|
let poh_recorder =
|
|
|
|
PohRecorder::new(bank.tick_height(), entry_sender, prev_id, std::u64::MAX);
|
2018-12-05 12:49:41 -08:00
|
|
|
let exit = Arc::new(AtomicBool::new(false));
|
|
|
|
|
|
|
|
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-01-28 14:52:35 -08:00
|
|
|
poh_recorder.record(h1, vec![tx]).unwrap();
|
2018-12-05 12:49:41 -08:00
|
|
|
|
|
|
|
if exit.load(Ordering::Relaxed) {
|
|
|
|
break Ok(());
|
|
|
|
}
|
|
|
|
}
|
2018-12-07 20:01:28 -07:00
|
|
|
})
|
|
|
|
.unwrap()
|
2018-12-05 12:49:41 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
const HASHES_PER_TICK: u64 = 2;
|
2019-02-09 09:25:11 -08:00
|
|
|
let poh_service = PohService::new(
|
2019-02-18 06:33:07 -08:00
|
|
|
bank,
|
2019-02-09 09:25:11 -08:00
|
|
|
poh_recorder,
|
|
|
|
PohServiceConfig::Tick(HASHES_PER_TICK as usize),
|
2019-02-17 20:15:34 -07:00
|
|
|
Arc::new(AtomicBool::new(false)),
|
2019-02-09 09:25:11 -08:00
|
|
|
);
|
2018-12-05 12:49:41 -08:00
|
|
|
|
|
|
|
// 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() {
|
|
|
|
if entry.is_tick() {
|
|
|
|
assert!(entry.num_hashes <= HASHES_PER_TICK);
|
|
|
|
|
|
|
|
if entry.num_hashes == HASHES_PER_TICK {
|
|
|
|
need_tick = false;
|
|
|
|
} else {
|
|
|
|
need_partial = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashes += entry.num_hashes;
|
|
|
|
|
|
|
|
assert_eq!(hashes, HASHES_PER_TICK);
|
|
|
|
|
|
|
|
hashes = 0;
|
|
|
|
} else {
|
|
|
|
assert!(entry.num_hashes >= 1);
|
|
|
|
need_entry = false;
|
|
|
|
hashes += entry.num_hashes - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exit.store(true, Ordering::Relaxed);
|
|
|
|
poh_service.exit();
|
2019-01-28 14:52:35 -08:00
|
|
|
let _ = poh_service.join().unwrap();
|
|
|
|
let _ = entry_producer.join().unwrap();
|
2018-12-05 12:49:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|