step one of lastidnotfound: record_stage->record_service, trim recorder to hashes (#1281)
step one of lastidnotfound * record_stage->record_service, trim recorder to hashes * doc updates, hash multiple without alloc() cc #1171
This commit is contained in:
parent
a6c15684c9
commit
62a18d4c02
15
src/hash.rs
15
src/hash.rs
|
@ -26,16 +26,23 @@ impl fmt::Display for Hash {
|
||||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Return a Sha256 hash for the given data.
|
|
||||||
pub fn hash(val: &[u8]) -> Hash {
|
|
||||||
let mut hasher = Sha256::default();
|
|
||||||
hasher.input(val);
|
|
||||||
|
|
||||||
|
/// Return a Sha256 hash for the given data.
|
||||||
|
pub fn hashv(vals: &[&[u8]]) -> Hash {
|
||||||
|
let mut hasher = Sha256::default();
|
||||||
|
for val in vals {
|
||||||
|
hasher.input(val);
|
||||||
|
}
|
||||||
// At the time of this writing, the sha2 library is stuck on an old version
|
// At the time of this writing, the sha2 library is stuck on an old version
|
||||||
// of generic_array (0.9.0). Decouple ourselves with a clone to our version.
|
// of generic_array (0.9.0). Decouple ourselves with a clone to our version.
|
||||||
Hash(GenericArray::clone_from_slice(hasher.result().as_slice()))
|
Hash(GenericArray::clone_from_slice(hasher.result().as_slice()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a Sha256 hash for the given data.
|
||||||
|
pub fn hash(val: &[u8]) -> Hash {
|
||||||
|
hashv(&[val])
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the hash of the given hash extended with the given value.
|
/// Return the hash of the given hash extended with the given value.
|
||||||
pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
|
pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
|
||||||
let mut hash_data = id.as_ref().to_vec();
|
let mut hash_data = id.as_ref().to_vec();
|
||||||
|
|
|
@ -36,6 +36,8 @@ pub mod ncp;
|
||||||
pub mod netutil;
|
pub mod netutil;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod payment_plan;
|
pub mod payment_plan;
|
||||||
|
pub mod poh;
|
||||||
|
pub mod poh_service;
|
||||||
pub mod record_stage;
|
pub mod record_stage;
|
||||||
pub mod recorder;
|
pub mod recorder;
|
||||||
pub mod recvmmsg;
|
pub mod recvmmsg;
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
//! The `Poh` module provides an object for generating a Proof of History.
|
||||||
|
//! It records Hashes items on behalf of its users.
|
||||||
|
|
||||||
|
use hash::{hash, hashv, Hash};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
pub struct Poh {
|
||||||
|
last_hash: Hash,
|
||||||
|
num_hashes: u64,
|
||||||
|
last_tick: Instant,
|
||||||
|
tick_duration: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PohEntry {
|
||||||
|
pub num_hashes: u64,
|
||||||
|
pub id: Hash,
|
||||||
|
pub mixin: Option<Hash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Poh {
|
||||||
|
pub fn new(last_hash: Hash, tick_duration: Option<Duration>) -> Self {
|
||||||
|
let last_tick = Instant::now();
|
||||||
|
Poh {
|
||||||
|
last_hash,
|
||||||
|
num_hashes: 0,
|
||||||
|
last_tick,
|
||||||
|
tick_duration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(&mut self) {
|
||||||
|
self.last_hash = hash(&self.last_hash.as_ref());
|
||||||
|
self.num_hashes += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record(&mut self, mixin: Hash) -> PohEntry {
|
||||||
|
let num_hashes = self.num_hashes + 1;
|
||||||
|
self.num_hashes = 0;
|
||||||
|
|
||||||
|
self.last_hash = hashv(&[&self.last_hash.as_ref(), &mixin.as_ref()]);
|
||||||
|
|
||||||
|
PohEntry {
|
||||||
|
num_hashes,
|
||||||
|
id: self.last_hash,
|
||||||
|
mixin: Some(mixin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emissions of Ticks (i.e. PohEntries without a mixin) allows
|
||||||
|
// validators to parallelize the work of catching up
|
||||||
|
pub fn tick(&mut self) -> Option<PohEntry> {
|
||||||
|
if let Some(tick_duration) = self.tick_duration {
|
||||||
|
if self.last_tick.elapsed() >= tick_duration {
|
||||||
|
self.last_tick = Instant::now();
|
||||||
|
let entry = PohEntry {
|
||||||
|
num_hashes: self.num_hashes,
|
||||||
|
id: self.last_hash,
|
||||||
|
mixin: None,
|
||||||
|
};
|
||||||
|
self.num_hashes = 0;
|
||||||
|
return Some(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(initial: Hash, entries: &[PohEntry]) -> bool {
|
||||||
|
let mut last_hash = initial;
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
assert!(entry.num_hashes != 0);
|
||||||
|
for _ in 1..entry.num_hashes {
|
||||||
|
last_hash = hash(&last_hash.as_ref());
|
||||||
|
}
|
||||||
|
let id = match entry.mixin {
|
||||||
|
Some(mixin) => hashv(&[&last_hash.as_ref(), &mixin.as_ref()]),
|
||||||
|
None => hash(&last_hash.as_ref()),
|
||||||
|
};
|
||||||
|
if id != entry.id {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
last_hash = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use hash::Hash;
|
||||||
|
use poh::{self, PohEntry};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_poh_verify_assert() {
|
||||||
|
poh::verify(
|
||||||
|
Hash::default(),
|
||||||
|
&[PohEntry {
|
||||||
|
num_hashes: 0,
|
||||||
|
id: Hash::default(),
|
||||||
|
mixin: None,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
//! The `poh_service` module provides an object for generating a Proof of History.
|
||||||
|
//! It records Hashes items on behalf of its users. It continuously generates
|
||||||
|
//! new Hashes, only stopping to check if it has been sent a Hash to mix in
|
||||||
|
//! to the Poh.
|
||||||
|
//!
|
||||||
|
//! The returned Entry includes the mix-in request, the latest Poh Hash, and the
|
||||||
|
//! number of Hashes generated in the service since the last mix-in request.
|
||||||
|
//!
|
||||||
|
//! The resulting stream of Hashes represents ordered events in time.
|
||||||
|
//!
|
||||||
|
use hash::Hash;
|
||||||
|
use poh::{Poh, PohEntry};
|
||||||
|
use service::Service;
|
||||||
|
use std::sync::mpsc::{channel, Receiver, RecvError, Sender, TryRecvError};
|
||||||
|
use std::thread::{self, Builder, JoinHandle};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub struct PohService {
|
||||||
|
thread_hdl: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PohService {
|
||||||
|
/// A background thread that will continue tagging received Transaction messages and
|
||||||
|
/// sending back Entry messages until either the receiver or sender channel is closed.
|
||||||
|
pub fn new(start_hash: Hash, hash_receiver: Receiver<Hash>) -> (Self, Receiver<PohEntry>) {
|
||||||
|
let (poh_sender, poh_receiver) = channel();
|
||||||
|
let thread_hdl = Builder::new()
|
||||||
|
.name("solana-record-service".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let mut poh = Poh::new(start_hash, None);
|
||||||
|
let _ = Self::process_hashes(&mut poh, &hash_receiver, &poh_sender);
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
(PohService { thread_hdl }, poh_receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `PohService::new`, but will automatically produce entries every `tick_duration`.
|
||||||
|
pub fn new_with_clock(
|
||||||
|
start_hash: Hash,
|
||||||
|
hash_receiver: Receiver<Hash>,
|
||||||
|
tick_duration: Duration,
|
||||||
|
) -> (Self, Receiver<PohEntry>) {
|
||||||
|
let (poh_sender, poh_receiver) = channel();
|
||||||
|
let thread_hdl = Builder::new()
|
||||||
|
.name("solana-record-service".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let mut poh = Poh::new(start_hash, Some(tick_duration));
|
||||||
|
loop {
|
||||||
|
if Self::try_process_hashes(&mut poh, &hash_receiver, &poh_sender).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
poh.hash();
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
(PohService { thread_hdl }, poh_receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_hash(hash: Hash, poh: &mut Poh, sender: &Sender<PohEntry>) -> Result<(), ()> {
|
||||||
|
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(
|
||||||
|
poh: &mut Poh,
|
||||||
|
receiver: &Receiver<Hash>,
|
||||||
|
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(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for PohService {
|
||||||
|
type JoinReturnType = ();
|
||||||
|
|
||||||
|
fn join(self) -> thread::Result<()> {
|
||||||
|
self.thread_hdl.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use poh::verify;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::thread::sleep;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_poh() {
|
||||||
|
let (hash_sender, hash_receiver) = channel();
|
||||||
|
let (poh_service, poh_receiver) = PohService::new(Hash::default(), hash_receiver);
|
||||||
|
|
||||||
|
hash_sender.send(Hash::default()).unwrap();
|
||||||
|
sleep(Duration::from_millis(1));
|
||||||
|
hash_sender.send(Hash::default()).unwrap();
|
||||||
|
sleep(Duration::from_millis(1));
|
||||||
|
hash_sender.send(Hash::default()).unwrap();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
drop(hash_sender);
|
||||||
|
assert_eq!(poh_service.thread_hdl.join().unwrap(), ());
|
||||||
|
|
||||||
|
assert!(verify(Hash::default(), &[entry0, entry1, entry2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_poh_closed_sender() {
|
||||||
|
let (hash_sender, hash_receiver) = channel();
|
||||||
|
let (poh_service, poh_receiver) = PohService::new(Hash::default(), hash_receiver);
|
||||||
|
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_with_clock(Hash::default(), hash_receiver, Duration::from_millis(1));
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(3));
|
||||||
|
drop(hash_sender);
|
||||||
|
let pohs: Vec<_> = poh_receiver.iter().map(|x| x).collect();
|
||||||
|
assert!(pohs.len() > 1);
|
||||||
|
|
||||||
|
assert!(verify(Hash::default(), &pohs));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue