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:
parent
8676b5d40c
commit
0c4cb76acf
|
@ -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);
|
||||||
|
})
|
||||||
|
}
|
|
@ -16,6 +16,13 @@ use std::borrow::Borrow;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use std::sync::{Arc, RwLock};
|
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 EntrySender = Sender<Vec<Entry>>;
|
||||||
pub type EntryReceiver = Receiver<Vec<Entry>>;
|
pub type EntryReceiver = Receiver<Vec<Entry>>;
|
||||||
|
|
||||||
|
@ -212,6 +219,7 @@ pub trait EntrySlice {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntrySlice for [Entry] {
|
impl EntrySlice for [Entry] {
|
||||||
|
#[cfg(not(feature = "cuda"))]
|
||||||
fn verify(&self, start_hash: &Hash) -> bool {
|
fn verify(&self, start_hash: &Hash) -> bool {
|
||||||
let genesis = [Entry {
|
let genesis = [Entry {
|
||||||
num_hashes: 0,
|
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> {
|
fn to_blobs(&self) -> Vec<Blob> {
|
||||||
split_serializable_chunks(
|
split_serializable_chunks(
|
||||||
&self,
|
&self,
|
||||||
|
@ -556,6 +636,53 @@ mod tests {
|
||||||
assert!(!bad_ticks.verify(&zero)); // inductive step, bad
|
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> {
|
fn blob_sized_entries(num_entries: usize) -> Vec<Entry> {
|
||||||
// rough guess
|
// rough guess
|
||||||
let mut magic_len = BLOB_DATA_SIZE
|
let mut magic_len = BLOB_DATA_SIZE
|
||||||
|
|
|
@ -17,6 +17,9 @@ use solana_sdk::signature::Signature;
|
||||||
use solana_sdk::transaction::Transaction;
|
use solana_sdk::transaction::Transaction;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
#[cfg(feature = "cuda")]
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
pub const NUM_THREADS: u32 = 10;
|
pub const NUM_THREADS: u32 = 10;
|
||||||
use std::cell::RefCell;
|
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_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 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"))]
|
#[cfg(not(feature = "cuda"))]
|
||||||
|
|
|
@ -17,7 +17,7 @@ if [[ ! -d target/perf-libs ]]; then
|
||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
cd target/perf-libs
|
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
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
//! The `hash` module provides functions for creating SHA-256 hashes.
|
//! The `hash` module provides functions for creating SHA-256 hashes.
|
||||||
|
|
||||||
use bs58;
|
use bs58;
|
||||||
use generic_array::typenum::U32;
|
|
||||||
use generic_array::GenericArray;
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[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)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Hasher {
|
pub struct Hasher {
|
||||||
|
@ -28,9 +28,7 @@ impl Hasher {
|
||||||
pub fn result(self) -> Hash {
|
pub fn result(self) -> Hash {
|
||||||
// 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(
|
Hash(<[u8; 32]>::try_from(self.hasher.result().as_slice()).unwrap())
|
||||||
self.hasher.result().as_slice(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +73,7 @@ impl FromStr for Hash {
|
||||||
|
|
||||||
impl Hash {
|
impl Hash {
|
||||||
pub fn new(hash_slice: &[u8]) -> Self {
|
pub fn new(hash_slice: &[u8]) -> Self {
|
||||||
Hash(GenericArray::clone_from_slice(&hash_slice))
|
Hash(<[u8; 32]>::try_from(hash_slice).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue