From 0c4cb76acffb350342639a336c5006a2d2095b50 Mon Sep 17 00:00:00 2001 From: TristanDebrunner Date: Sat, 8 Jun 2019 10:21:43 -0600 Subject: [PATCH] Add GPU based PoH verification (#4524) * Add GPU poh verify * Switch to single PoH verify function * Add EntrySlice verify tests with hashes and txs * Add poh-verify benchmarks --- core/benches/poh_verify.rs | 48 ++++++++++++++ core/src/entry.rs | 127 +++++++++++++++++++++++++++++++++++++ core/src/sigverify.rs | 10 +++ fetch-perf-libs.sh | 2 +- sdk/src/hash.rs | 12 ++-- 5 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 core/benches/poh_verify.rs diff --git a/core/benches/poh_verify.rs b/core/benches/poh_verify.rs new file mode 100644 index 000000000..22db1d352 --- /dev/null +++ b/core/benches/poh_verify.rs @@ -0,0 +1,48 @@ +#![feature(test)] +extern crate test; + +use solana::entry::EntrySlice; +use solana::entry::{next_entry_mut, Entry}; +use solana_sdk::hash::{hash, Hash}; +use solana_sdk::signature::{Keypair, KeypairUtil}; +use solana_sdk::system_transaction; +use test::Bencher; + +const NUM_HASHES: u64 = 400; +const NUM_ENTRIES: usize = 800; + +#[bench] +fn bench_poh_verify_ticks(bencher: &mut Bencher) { + let zero = Hash::default(); + let mut cur_hash = hash(&zero.as_ref()); + let start = *&cur_hash; + + let mut ticks: Vec = Vec::with_capacity(NUM_ENTRIES); + for _ in 0..NUM_ENTRIES { + ticks.push(next_entry_mut(&mut cur_hash, NUM_HASHES, vec![])); + } + + bencher.iter(|| { + ticks.verify(&start); + }) +} + +#[bench] +fn bench_poh_verify_transaction_entries(bencher: &mut Bencher) { + let zero = Hash::default(); + let mut cur_hash = hash(&zero.as_ref()); + let start = *&cur_hash; + + let keypair1 = Keypair::new(); + let pubkey1 = keypair1.pubkey(); + + let mut ticks: Vec = Vec::with_capacity(NUM_ENTRIES); + for _ in 0..NUM_ENTRIES { + let tx = system_transaction::create_user_account(&keypair1, &pubkey1, 42, cur_hash); + ticks.push(next_entry_mut(&mut cur_hash, NUM_HASHES, vec![tx])); + } + + bencher.iter(|| { + ticks.verify(&start); + }) +} diff --git a/core/src/entry.rs b/core/src/entry.rs index 93edca4e4..76349bb59 100644 --- a/core/src/entry.rs +++ b/core/src/entry.rs @@ -16,6 +16,13 @@ use std::borrow::Borrow; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, RwLock}; +#[cfg(feature = "cuda")] +use crate::sigverify::poh_verify_many; +#[cfg(feature = "cuda")] +use std::sync::Mutex; +#[cfg(feature = "cuda")] +use std::thread; + pub type EntrySender = Sender>; pub type EntryReceiver = Receiver>; @@ -212,6 +219,7 @@ pub trait EntrySlice { } impl EntrySlice for [Entry] { + #[cfg(not(feature = "cuda"))] fn verify(&self, start_hash: &Hash) -> bool { let genesis = [Entry { num_hashes: 0, @@ -233,6 +241,78 @@ impl EntrySlice for [Entry] { }) } + #[cfg(feature = "cuda")] + fn verify(&self, start_hash: &Hash) -> bool { + let genesis = [Entry { + num_hashes: 0, + hash: *start_hash, + transactions: vec![], + }]; + + let hashes: Vec = genesis + .par_iter() + .chain(self) + .map(|entry| entry.hash) + .take(self.len()) + .collect(); + + let num_hashes_vec: Vec = self + .into_iter() + .map(|entry| entry.num_hashes.saturating_sub(1)) + .collect(); + + let length = self.len(); + let hashes = Arc::new(Mutex::new(hashes)); + let hashes_clone = hashes.clone(); + + let gpu_verify_thread = thread::spawn(move || { + let mut hashes = hashes_clone.lock().unwrap(); + let res; + unsafe { + res = poh_verify_many( + hashes.as_mut_ptr() as *mut u8, + num_hashes_vec.as_ptr(), + length, + 1, + ); + } + if res != 0 { + panic!("GPU PoH verify many failed"); + } + }); + + let tx_hashes: Vec> = self + .into_par_iter() + .map(|entry| { + if entry.transactions.is_empty() { + None + } else { + Some(hash_transactions(&entry.transactions)) + } + }) + .collect(); + + gpu_verify_thread.join().unwrap(); + + let hashes = Arc::try_unwrap(hashes).unwrap().into_inner().unwrap(); + hashes + .into_par_iter() + .zip(tx_hashes) + .zip(self) + .all(|((hash, tx_hash), answer)| { + if answer.num_hashes == 0 { + hash == answer.hash + } else { + let mut poh = Poh::new(hash, None); + if let Some(mixin) = tx_hash { + poh.record(mixin).unwrap().hash == answer.hash + } else { + poh.tick().unwrap().hash == answer.hash + } + } + }) + } + fn to_blobs(&self) -> Vec { split_serializable_chunks( &self, @@ -556,6 +636,53 @@ mod tests { assert!(!bad_ticks.verify(&zero)); // inductive step, bad } + #[test] + fn test_verify_slice_with_hashes() { + solana_logger::setup(); + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + let two = hash(&one.as_ref()); + assert!(vec![][..].verify(&one)); // base case + assert!(vec![Entry::new_tick(1, &two)][..].verify(&one)); // singleton case 1 + assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two)); // singleton case 2, bad + + let mut ticks = vec![next_entry(&one, 1, vec![])]; + ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![])); + assert!(ticks.verify(&one)); // inductive step + + let mut bad_ticks = vec![next_entry(&one, 1, vec![])]; + bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![])); + bad_ticks[1].hash = one; + assert!(!bad_ticks.verify(&one)); // inductive step, bad + } + + #[test] + fn test_verify_slice_with_hashes_and_transactions() { + solana_logger::setup(); + let zero = Hash::default(); + let one = hash(&zero.as_ref()); + let two = hash(&one.as_ref()); + let alice_pubkey = Keypair::default(); + let tx0 = create_sample_payment(&alice_pubkey, one); + let tx1 = create_sample_timestamp(&alice_pubkey, one); + assert!(vec![][..].verify(&one)); // base case + assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one)); // singleton case 1 + assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two)); // singleton case 2, bad + + let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])]; + ticks.push(next_entry( + &ticks.last().unwrap().hash, + 1, + vec![tx1.clone()], + )); + assert!(ticks.verify(&one)); // inductive step + + let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])]; + bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1])); + bad_ticks[1].hash = one; + assert!(!bad_ticks.verify(&one)); // inductive step, bad + } + fn blob_sized_entries(num_entries: usize) -> Vec { // rough guess let mut magic_len = BLOB_DATA_SIZE diff --git a/core/src/sigverify.rs b/core/src/sigverify.rs index 8ed0f0349..4a853e660 100644 --- a/core/src/sigverify.rs +++ b/core/src/sigverify.rs @@ -17,6 +17,9 @@ use solana_sdk::signature::Signature; use solana_sdk::transaction::Transaction; use std::mem::size_of; +#[cfg(feature = "cuda")] +use std::os::raw::c_int; + pub const NUM_THREADS: u32 = 10; use std::cell::RefCell; @@ -68,6 +71,13 @@ extern "C" { pub fn chacha_init_sha_state(sha_state: *mut u8, num_keys: u32); pub fn chacha_end_sha_state(sha_state_in: *const u8, out: *mut u8, num_keys: u32); + + pub fn poh_verify_many( + hashes: *mut u8, + num_hashes_arr: *const u64, + num_elems: usize, + use_non_default_stream: u8, + ) -> c_int; } #[cfg(not(feature = "cuda"))] diff --git a/fetch-perf-libs.sh b/fetch-perf-libs.sh index 4a9e9188e..7908f9497 100755 --- a/fetch-perf-libs.sh +++ b/fetch-perf-libs.sh @@ -17,7 +17,7 @@ if [[ ! -d target/perf-libs ]]; then ( set -x cd target/perf-libs - curl https://solana-perf.s3.amazonaws.com/v0.12.1/x86_64-unknown-linux-gnu/solana-perf.tgz | tar zxvf - + curl https://solana-perf.s3.amazonaws.com/v0.13.2/x86_64-unknown-linux-gnu/solana-perf.tgz | tar zxvf - ) fi diff --git a/sdk/src/hash.rs b/sdk/src/hash.rs index ffe008fa1..0b5fab7b9 100644 --- a/sdk/src/hash.rs +++ b/sdk/src/hash.rs @@ -1,15 +1,15 @@ //! The `hash` module provides functions for creating SHA-256 hashes. use bs58; -use generic_array::typenum::U32; -use generic_array::GenericArray; use sha2::{Digest, Sha256}; +use std::convert::TryFrom; use std::fmt; use std::mem; use std::str::FromStr; #[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Hash(GenericArray); +#[repr(transparent)] +pub struct Hash([u8; 32]); #[derive(Clone, Default)] pub struct Hasher { @@ -28,9 +28,7 @@ impl Hasher { pub fn result(self) -> Hash { // 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. - Hash(GenericArray::clone_from_slice( - self.hasher.result().as_slice(), - )) + Hash(<[u8; 32]>::try_from(self.hasher.result().as_slice()).unwrap()) } } @@ -75,7 +73,7 @@ impl FromStr for Hash { impl Hash { pub fn new(hash_slice: &[u8]) -> Self { - Hash(GenericArray::clone_from_slice(&hash_slice)) + Hash(<[u8; 32]>::try_from(hash_slice).unwrap()) } }