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
This commit is contained in:
TristanDebrunner 2019-06-08 10:21:43 -06:00 committed by GitHub
parent 8676b5d40c
commit 0c4cb76acf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 8 deletions

View File

@ -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<Entry> = 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<Entry> = 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);
})
}

View File

@ -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<Vec<Entry>>;
pub type EntryReceiver = Receiver<Vec<Entry>>;
@ -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<Hash> = genesis
.par_iter()
.chain(self)
.map(|entry| entry.hash)
.take(self.len())
.collect();
let num_hashes_vec: Vec<u64> = 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<Option<Hash>> = 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<Blob> {
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<Entry> {
// rough guess
let mut magic_len = BLOB_DATA_SIZE

View File

@ -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"))]

View File

@ -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

View File

@ -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<u8, U32>);
#[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())
}
}