2018-09-26 05:52:13 -07:00
//! The `poh_recorder` module provides an object for synchronizing with Proof of History.
//! It synchronizes PoH, bank's register_entry_id and the ledger
//!
use bank ::Bank ;
use entry ::Entry ;
use hash ::Hash ;
use poh ::Poh ;
2018-10-18 22:57:48 -07:00
use result ::{ Error , Result } ;
2018-09-26 05:52:13 -07:00
use std ::sync ::mpsc ::Sender ;
use std ::sync ::{ Arc , Mutex } ;
use transaction ::Transaction ;
2018-10-18 22:57:48 -07:00
#[ derive(Debug, PartialEq, Eq, Clone) ]
pub enum PohRecorderError {
2018-10-25 14:56:21 -07:00
InvalidCallingObject ,
2018-10-18 22:57:48 -07:00
MaxHeightReached ,
}
2018-09-26 05:52:13 -07:00
#[ derive(Clone) ]
pub struct PohRecorder {
2018-10-25 14:56:21 -07:00
is_virtual : bool ,
virtual_tick_entries : Arc < Mutex < Vec < Entry > > > ,
2018-09-26 05:52:13 -07:00
poh : Arc < Mutex < Poh > > ,
bank : Arc < Bank > ,
sender : Sender < Vec < Entry > > ,
2018-10-18 22:57:48 -07:00
// TODO: whe extracting PoH generator into a separate standalone service,
// use this field for checking timeouts when running as a validator, and as
// a transmission guard when running as the leader.
pub max_tick_height : Option < u64 > ,
2018-09-26 05:52:13 -07:00
}
impl PohRecorder {
2018-10-18 22:57:48 -07:00
pub fn hash ( & self ) -> Result < ( ) > {
2018-09-26 05:52:13 -07:00
// 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-10-25 14:56:21 -07:00
if self . is_max_tick_height_reached ( & * poh ) {
2018-10-18 22:57:48 -07:00
Err ( Error ::PohRecorderError ( PohRecorderError ::MaxHeightReached ) )
} else {
poh . hash ( ) ;
Ok ( ( ) )
}
2018-09-26 05:52:13 -07:00
}
2018-10-25 14:56:21 -07:00
pub fn tick ( & mut self ) -> Result < ( ) > {
2018-10-18 22:57:48 -07:00
// Register and send the entry out while holding the lock if the max PoH height
// hasn't been reached.
2018-09-26 05:52:13 -07:00
// This guarantees PoH order and Entry production and banks LastId queue is the same
let mut poh = self . poh . lock ( ) . unwrap ( ) ;
2018-10-25 14:56:21 -07:00
if self . is_max_tick_height_reached ( & * poh ) {
2018-10-18 22:57:48 -07:00
Err ( Error ::PohRecorderError ( PohRecorderError ::MaxHeightReached ) )
2018-10-25 14:56:21 -07:00
} else if self . is_virtual {
self . generate_and_store_tick ( & mut * poh ) ;
Ok ( ( ) )
2018-10-18 22:57:48 -07:00
} else {
self . register_and_send_tick ( & mut * poh ) ? ;
2018-10-25 14:56:21 -07:00
Ok ( ( ) )
2018-10-18 22:57:48 -07:00
}
2018-09-26 05:52:13 -07:00
}
pub fn record ( & self , mixin : Hash , txs : Vec < Transaction > ) -> Result < ( ) > {
2018-10-25 14:56:21 -07:00
if self . is_virtual {
return Err ( Error ::PohRecorderError (
PohRecorderError ::InvalidCallingObject ,
) ) ;
}
2018-09-26 05:52:13 -07:00
// 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-10-25 14:56:21 -07:00
if self . is_max_tick_height_reached ( & * poh ) {
2018-10-18 22:57:48 -07:00
Err ( Error ::PohRecorderError ( PohRecorderError ::MaxHeightReached ) )
} else {
self . record_and_send_txs ( & mut * poh , mixin , txs ) ? ;
Ok ( ( ) )
}
}
2018-10-25 14:56:21 -07:00
/// 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 (
bank : Arc < Bank > ,
sender : Sender < Vec < Entry > > ,
last_entry_id : Hash ,
max_tick_height : Option < u64 > ,
is_virtual : bool ,
virtual_tick_entries : Vec < Entry > ,
) -> Self {
2018-10-30 10:05:18 -07:00
let poh = Arc ::new ( Mutex ::new ( Poh ::new ( last_entry_id , bank . get_tick_height ( ) ) ) ) ;
2018-10-25 14:56:21 -07:00
let virtual_tick_entries = Arc ::new ( Mutex ::new ( virtual_tick_entries ) ) ;
PohRecorder {
poh ,
bank ,
sender ,
max_tick_height ,
is_virtual ,
virtual_tick_entries ,
}
}
fn generate_tick_entry ( & self , poh : & mut Poh ) -> Entry {
let tick = poh . tick ( ) ;
Entry {
num_hashes : tick . num_hashes ,
id : tick . id ,
transactions : vec ! [ ] ,
}
}
fn is_max_tick_height_reached ( & self , poh : & Poh ) -> bool {
2018-10-18 22:57:48 -07:00
if let Some ( max_tick_height ) = self . max_tick_height {
poh . tick_height > = max_tick_height
} else {
false
}
}
fn record_and_send_txs ( & self , poh : & mut Poh , mixin : Hash , txs : Vec < Transaction > ) -> Result < ( ) > {
2018-09-26 05:52:13 -07:00
let tick = poh . record ( mixin ) ;
2018-10-10 17:23:06 -07:00
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 " ) ;
2018-09-26 05:52:13 -07:00
let entry = Entry {
num_hashes : tick . num_hashes ,
id : tick . id ,
transactions : txs ,
} ;
self . sender . send ( vec! [ entry ] ) ? ;
Ok ( ( ) )
}
2018-10-18 22:57:48 -07:00
2018-10-25 14:56:21 -07:00
fn generate_and_store_tick ( & self , poh : & mut Poh ) {
let tick_entry = self . generate_tick_entry ( poh ) ;
self . virtual_tick_entries . lock ( ) . unwrap ( ) . push ( tick_entry ) ;
}
2018-10-18 22:57:48 -07:00
fn register_and_send_tick ( & self , poh : & mut Poh ) -> Result < ( ) > {
2018-10-25 14:56:21 -07:00
let tick_entry = self . generate_tick_entry ( poh ) ;
self . bank . register_entry_id ( & tick_entry . id ) ;
self . sender . send ( vec! [ tick_entry ] ) ? ;
2018-10-18 22:57:48 -07:00
Ok ( ( ) )
}
2018-09-26 05:52:13 -07:00
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use hash ::hash ;
use mint ::Mint ;
use std ::sync ::mpsc ::channel ;
use std ::sync ::Arc ;
2018-10-10 17:23:06 -07:00
use system_transaction ::test_tx ;
2018-09-26 05:52:13 -07:00
#[ test ]
fn test_poh ( ) {
let mint = Mint ::new ( 1 ) ;
let bank = Arc ::new ( Bank ::new ( & mint ) ) ;
2018-10-12 00:39:10 -07:00
let last_id = bank . last_id ( ) ;
2018-09-26 05:52:13 -07:00
let ( entry_sender , entry_receiver ) = channel ( ) ;
2018-10-30 10:05:18 -07:00
let mut poh_recorder = PohRecorder ::new ( bank , entry_sender , last_id , None , false , vec! [ ] ) ;
2018-09-26 05:52:13 -07:00
//send some data
let h1 = hash ( b " hello world! " ) ;
2018-10-10 17:23:06 -07:00
let tx = test_tx ( ) ;
assert! ( poh_recorder . record ( h1 , vec! [ tx ] ) . is_ok ( ) ) ;
2018-09-26 05:52:13 -07:00
assert! ( poh_recorder . tick ( ) . is_ok ( ) ) ;
//get some events
let _ = entry_receiver . recv ( ) . unwrap ( ) ;
let _ = entry_receiver . recv ( ) . unwrap ( ) ;
//make sure it handles channel close correctly
drop ( entry_receiver ) ;
assert! ( poh_recorder . tick ( ) . is_err ( ) ) ;
}
}