1use {
6 crate::poh::Poh,
7 crossbeam_channel::{Receiver, Sender},
8 dlopen2::symbor::{Container, SymBorApi, Symbol},
9 log::*,
10 rand::{thread_rng, Rng},
11 rayon::{prelude::*, ThreadPool},
12 serde::{Deserialize, Serialize},
13 solana_hash::Hash,
14 solana_measure::measure::Measure,
15 solana_merkle_tree::MerkleTree,
16 solana_metrics::*,
17 solana_packet::Meta,
18 solana_perf::{
19 cuda_runtime::PinnedVec,
20 packet::{Packet, PacketBatch, PacketBatchRecycler, PinnedPacketBatch, PACKETS_PER_BATCH},
21 perf_libs,
22 recycler::Recycler,
23 sigverify,
24 },
25 solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
26 solana_transaction::{
27 versioned::VersionedTransaction, Transaction, TransactionVerificationMode,
28 },
29 solana_transaction_error::{TransactionError, TransactionResult as Result},
30 std::{
31 cmp,
32 ffi::OsStr,
33 iter::repeat_with,
34 sync::{Arc, Mutex, Once, OnceLock},
35 thread::{self, JoinHandle},
36 time::Instant,
37 },
38};
39
40pub type EntrySender = Sender<Vec<Entry>>;
41pub type EntryReceiver = Receiver<Vec<Entry>>;
42
43static API: OnceLock<Container<Api>> = OnceLock::new();
44
45pub fn init_poh() {
46 init(OsStr::new("libpoh-simd.so"));
47}
48
49fn init(name: &OsStr) {
50 static INIT_HOOK: Once = Once::new();
51
52 info!("Loading {:?}", name);
53 INIT_HOOK.call_once(|| {
54 let path;
55 let lib_name = if let Some(perf_libs_path) = solana_perf::perf_libs::locate_perf_libs() {
56 solana_perf::perf_libs::append_to_ld_library_path(
57 perf_libs_path.to_str().unwrap_or("").to_string(),
58 );
59 path = perf_libs_path.join(name);
60 path.as_os_str()
61 } else {
62 name
63 };
64
65 match unsafe { Container::load(lib_name) } {
66 Ok(api) => _ = API.set(api),
67 Err(err) => error!("Unable to load {lib_name:?}: {err}"),
68 }
69 })
70}
71
72pub fn api() -> Option<&'static Container<Api<'static>>> {
73 {
74 static INIT_HOOK: Once = Once::new();
75 INIT_HOOK.call_once(|| {
76 if std::env::var("TEST_PERF_LIBS").is_ok() {
77 init_poh()
78 }
79 });
80 }
81
82 API.get()
83}
84
85#[derive(SymBorApi)]
86pub struct Api<'a> {
87 pub poh_verify_many_simd_avx512skx:
88 Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
89 pub poh_verify_many_simd_avx2:
90 Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
91}
92
93#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)]
121pub struct Entry {
122 pub num_hashes: u64,
124
125 pub hash: Hash,
127
128 pub transactions: Vec<VersionedTransaction>,
132}
133
134pub struct EntrySummary {
135 pub num_hashes: u64,
136 pub hash: Hash,
137 pub num_transactions: u64,
138}
139
140impl From<&Entry> for EntrySummary {
141 fn from(entry: &Entry) -> Self {
142 Self {
143 num_hashes: entry.num_hashes,
144 hash: entry.hash,
145 num_transactions: entry.transactions.len() as u64,
146 }
147 }
148}
149
150pub enum EntryType<Tx: TransactionWithMeta> {
152 Transactions(Vec<Tx>),
153 Tick(Hash),
154}
155
156impl Entry {
157 pub fn new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec<Transaction>) -> Self {
159 if num_hashes == 0 && !transactions.is_empty() {
162 num_hashes = 1;
163 }
164
165 let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
166 let hash = next_hash(prev_hash, num_hashes, &transactions);
167 Entry {
168 num_hashes,
169 hash,
170 transactions,
171 }
172 }
173
174 pub fn new_mut(
175 start_hash: &mut Hash,
176 num_hashes: &mut u64,
177 transactions: Vec<Transaction>,
178 ) -> Self {
179 let entry = Self::new(start_hash, *num_hashes, transactions);
180 *start_hash = entry.hash;
181 *num_hashes = 0;
182
183 entry
184 }
185
186 #[cfg(test)]
187 pub fn new_tick(num_hashes: u64, hash: &Hash) -> Self {
188 Entry {
189 num_hashes,
190 hash: *hash,
191 transactions: vec![],
192 }
193 }
194
195 pub fn verify(&self, start_hash: &Hash) -> bool {
198 let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions);
199 if self.hash != ref_hash {
200 warn!(
201 "next_hash is invalid expected: {:?} actual: {:?}",
202 self.hash, ref_hash
203 );
204 return false;
205 }
206 true
207 }
208
209 pub fn is_tick(&self) -> bool {
210 self.transactions.is_empty()
211 }
212}
213
214pub fn hash_transactions(transactions: &[VersionedTransaction]) -> Hash {
215 let signatures: Vec<_> = transactions
217 .iter()
218 .flat_map(|tx| tx.signatures.iter())
219 .collect();
220 let merkle_tree = MerkleTree::new(&signatures);
221 if let Some(root_hash) = merkle_tree.get_root() {
222 *root_hash
223 } else {
224 Hash::default()
225 }
226}
227
228pub fn next_hash(
233 start_hash: &Hash,
234 num_hashes: u64,
235 transactions: &[VersionedTransaction],
236) -> Hash {
237 if num_hashes == 0 && transactions.is_empty() {
238 return *start_hash;
239 }
240
241 let mut poh = Poh::new(*start_hash, None);
242 poh.hash(num_hashes.saturating_sub(1));
243 if transactions.is_empty() {
244 poh.tick().unwrap().hash
245 } else {
246 poh.record(hash_transactions(transactions)).unwrap().hash
247 }
248}
249
250enum VerifyAction {
252 Mixin(Hash),
254 Tick,
256 None,
258}
259
260pub struct GpuVerificationData {
261 thread_h: Option<JoinHandle<u64>>,
262 hashes: Option<Arc<Mutex<PinnedVec<Hash>>>>,
263 verifications: Option<Vec<(VerifyAction, Hash)>>,
264}
265
266pub enum DeviceVerificationData {
267 Cpu(),
268 Gpu(GpuVerificationData),
269}
270
271pub struct EntryVerificationState {
272 verification_status: EntryVerificationStatus,
273 poh_duration_us: u64,
274 device_verification_data: DeviceVerificationData,
275}
276
277pub struct GpuSigVerificationData {
278 thread_h: Option<JoinHandle<(bool, u64)>>,
279}
280
281pub enum DeviceSigVerificationData {
282 Cpu(),
283 Gpu(GpuSigVerificationData),
284}
285
286pub struct EntrySigVerificationState<Tx: TransactionWithMeta> {
287 verification_status: EntryVerificationStatus,
288 entries: Option<Vec<EntryType<Tx>>>,
289 device_verification_data: DeviceSigVerificationData,
290 gpu_verify_duration_us: u64,
291}
292
293impl<Tx: TransactionWithMeta> EntrySigVerificationState<Tx> {
294 pub fn entries(&mut self) -> Option<Vec<EntryType<Tx>>> {
295 self.entries.take()
296 }
297 pub fn finish_verify(&mut self) -> bool {
298 match &mut self.device_verification_data {
299 DeviceSigVerificationData::Gpu(verification_state) => {
300 let (verified, gpu_time_us) =
301 verification_state.thread_h.take().unwrap().join().unwrap();
302 self.gpu_verify_duration_us = gpu_time_us;
303 self.verification_status = if verified {
304 EntryVerificationStatus::Success
305 } else {
306 EntryVerificationStatus::Failure
307 };
308 verified
309 }
310 DeviceSigVerificationData::Cpu() => {
311 self.verification_status == EntryVerificationStatus::Success
312 }
313 }
314 }
315 pub fn status(&self) -> EntryVerificationStatus {
316 self.verification_status
317 }
318 pub fn gpu_verify_duration(&self) -> u64 {
319 self.gpu_verify_duration_us
320 }
321}
322
323#[derive(Default, Clone)]
324pub struct VerifyRecyclers {
325 hash_recycler: Recycler<PinnedVec<Hash>>,
326 tick_count_recycler: Recycler<PinnedVec<u64>>,
327 packet_recycler: PacketBatchRecycler,
328 out_recycler: Recycler<PinnedVec<u8>>,
329 tx_offset_recycler: Recycler<sigverify::TxOffset>,
330}
331
332#[derive(PartialEq, Eq, Clone, Copy, Debug)]
333pub enum EntryVerificationStatus {
334 Failure,
335 Success,
336 Pending,
337}
338
339impl EntryVerificationState {
340 pub fn status(&self) -> EntryVerificationStatus {
341 self.verification_status
342 }
343
344 pub fn poh_duration_us(&self) -> u64 {
345 self.poh_duration_us
346 }
347
348 pub fn finish_verify(&mut self, thread_pool: &ThreadPool) -> bool {
349 match &mut self.device_verification_data {
350 DeviceVerificationData::Gpu(verification_state) => {
351 let gpu_time_us = verification_state.thread_h.take().unwrap().join().unwrap();
352
353 let mut verify_check_time = Measure::start("verify_check");
354 let hashes = verification_state.hashes.take().unwrap();
355 let hashes = Arc::try_unwrap(hashes)
356 .expect("unwrap Arc")
357 .into_inner()
358 .expect("into_inner");
359 let res = thread_pool.install(|| {
360 hashes
361 .into_par_iter()
362 .cloned()
363 .zip(verification_state.verifications.take().unwrap())
364 .all(|(hash, (action, expected))| {
365 let actual = match action {
366 VerifyAction::Mixin(mixin) => {
367 Poh::new(hash, None).record(mixin).unwrap().hash
368 }
369 VerifyAction::Tick => Poh::new(hash, None).tick().unwrap().hash,
370 VerifyAction::None => hash,
371 };
372 actual == expected
373 })
374 });
375 verify_check_time.stop();
376 self.poh_duration_us += gpu_time_us + verify_check_time.as_us();
377
378 self.verification_status = if res {
379 EntryVerificationStatus::Success
380 } else {
381 EntryVerificationStatus::Failure
382 };
383 res
384 }
385 DeviceVerificationData::Cpu() => {
386 self.verification_status == EntryVerificationStatus::Success
387 }
388 }
389 }
390}
391
392pub fn verify_transactions<Tx: TransactionWithMeta + Send + Sync>(
393 entries: Vec<Entry>,
394 thread_pool: &ThreadPool,
395 verify: Arc<dyn Fn(VersionedTransaction) -> Result<Tx> + Send + Sync>,
396) -> Result<Vec<EntryType<Tx>>> {
397 thread_pool.install(|| {
398 entries
399 .into_par_iter()
400 .map(|entry| {
401 if entry.transactions.is_empty() {
402 Ok(EntryType::Tick(entry.hash))
403 } else {
404 Ok(EntryType::Transactions(
405 entry
406 .transactions
407 .into_par_iter()
408 .map(verify.as_ref())
409 .collect::<Result<Vec<_>>>()?,
410 ))
411 }
412 })
413 .collect()
414 })
415}
416
417pub fn start_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
418 entries: Vec<Entry>,
419 skip_verification: bool,
420 thread_pool: &ThreadPool,
421 verify_recyclers: VerifyRecyclers,
422 verify: Arc<
423 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
424 >,
425) -> Result<EntrySigVerificationState<Tx>> {
426 let api = perf_libs::api();
427
428 let use_cpu = skip_verification
435 || api.is_none()
436 || entries
437 .iter()
438 .try_fold(0, |accum: usize, entry: &Entry| -> Option<usize> {
439 if accum.saturating_add(entry.transactions.len()) < 512 {
440 Some(accum.saturating_add(entry.transactions.len()))
441 } else {
442 None
443 }
444 })
445 .is_some();
446
447 if use_cpu {
448 start_verify_transactions_cpu(entries, skip_verification, thread_pool, verify)
449 } else {
450 start_verify_transactions_gpu(entries, verify_recyclers, thread_pool, verify)
451 }
452}
453
454fn start_verify_transactions_cpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
455 entries: Vec<Entry>,
456 skip_verification: bool,
457 thread_pool: &ThreadPool,
458 verify: Arc<
459 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
460 >,
461) -> Result<EntrySigVerificationState<Tx>> {
462 let verify_func = {
463 let mode = if skip_verification {
464 TransactionVerificationMode::HashOnly
465 } else {
466 TransactionVerificationMode::FullVerification
467 };
468
469 move |versioned_tx| verify(versioned_tx, mode)
470 };
471
472 let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
473
474 Ok(EntrySigVerificationState {
475 verification_status: EntryVerificationStatus::Success,
476 entries: Some(entries),
477 device_verification_data: DeviceSigVerificationData::Cpu(),
478 gpu_verify_duration_us: 0,
479 })
480}
481
482fn start_verify_transactions_gpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
483 entries: Vec<Entry>,
484 verify_recyclers: VerifyRecyclers,
485 thread_pool: &ThreadPool,
486 verify: Arc<
487 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
488 >,
489) -> Result<EntrySigVerificationState<Tx>> {
490 let verify_func = {
491 move |versioned_tx: VersionedTransaction| -> Result<Tx> {
492 verify(versioned_tx, TransactionVerificationMode::HashOnly)
493 }
494 };
495
496 let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
497
498 let transactions = entries
499 .iter()
500 .filter_map(|entry_type| match entry_type {
501 EntryType::Tick(_) => None,
502 EntryType::Transactions(transactions) => Some(transactions),
503 })
504 .flatten()
505 .collect::<Vec<_>>();
506
507 if transactions.is_empty() {
508 return Ok(EntrySigVerificationState {
509 verification_status: EntryVerificationStatus::Success,
510 entries: Some(entries),
511 device_verification_data: DeviceSigVerificationData::Cpu(),
512 gpu_verify_duration_us: 0,
513 });
514 }
515
516 let packet_batches = thread_pool.install(|| {
517 transactions
518 .par_chunks(PACKETS_PER_BATCH)
519 .map(|transaction_chunk| {
520 let num_transactions = transaction_chunk.len();
521 let mut packet_batch = PinnedPacketBatch::new_with_recycler(
522 &verify_recyclers.packet_recycler,
523 num_transactions,
524 "entry-sig-verify",
525 );
526 unsafe {
532 packet_batch.set_len(num_transactions);
533 }
534 let transaction_iter = transaction_chunk
535 .iter()
536 .map(|tx| tx.to_versioned_transaction());
537
538 let res = packet_batch
539 .iter_mut()
540 .zip(transaction_iter)
541 .all(|(packet, tx)| {
542 *packet.meta_mut() = Meta::default();
543 Packet::populate_packet(packet, None, &tx).is_ok()
544 });
545 if res {
546 Ok(PacketBatch::from(packet_batch))
547 } else {
548 Err(TransactionError::SanitizeFailure)
549 }
550 })
551 .collect::<Result<Vec<_>>>()
552 });
553 let mut packet_batches = packet_batches?;
554
555 let tx_offset_recycler = verify_recyclers.tx_offset_recycler;
556 let out_recycler = verify_recyclers.out_recycler;
557 let num_packets = transactions.len();
558 let gpu_verify_thread = thread::Builder::new()
559 .name("solGpuSigVerify".into())
560 .spawn(move || {
561 let mut verify_time = Measure::start("sigverify");
562 sigverify::ed25519_verify(
563 &mut packet_batches,
564 &tx_offset_recycler,
565 &out_recycler,
566 false,
567 num_packets,
568 );
569 let verified = packet_batches
570 .iter()
571 .all(|batch| batch.iter().all(|p| !p.meta().discard()));
572 verify_time.stop();
573 (verified, verify_time.as_us())
574 })
575 .unwrap();
576
577 Ok(EntrySigVerificationState {
578 verification_status: EntryVerificationStatus::Pending,
579 entries: Some(entries),
580 device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData {
581 thread_h: Some(gpu_verify_thread),
582 }),
583 gpu_verify_duration_us: 0,
584 })
585}
586
587fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
588 let actual = if !ref_entry.transactions.is_empty() {
589 let tx_hash = hash_transactions(&ref_entry.transactions);
590 let mut poh = Poh::new(computed_hash, None);
591 poh.record(tx_hash).unwrap().hash
592 } else if ref_entry.num_hashes > 0 {
593 let mut poh = Poh::new(computed_hash, None);
594 poh.tick().unwrap().hash
595 } else {
596 computed_hash
597 };
598 actual == ref_entry.hash
599}
600
601pub trait EntrySlice {
603 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState;
605 fn verify_cpu_generic(
606 &self,
607 start_hash: &Hash,
608 thread_pool: &ThreadPool,
609 ) -> EntryVerificationState;
610 fn verify_cpu_x86_simd(
611 &self,
612 start_hash: &Hash,
613 simd_len: usize,
614 thread_pool: &ThreadPool,
615 ) -> EntryVerificationState;
616 fn start_verify(
617 &self,
618 start_hash: &Hash,
619 thread_pool: &ThreadPool,
620 recyclers: VerifyRecyclers,
621 ) -> EntryVerificationState;
622 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool;
623 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
627 fn tick_count(&self) -> u64;
629}
630
631impl EntrySlice for [Entry] {
632 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool {
633 self.start_verify(start_hash, thread_pool, VerifyRecyclers::default())
634 .finish_verify(thread_pool)
635 }
636
637 fn verify_cpu_generic(
638 &self,
639 start_hash: &Hash,
640 thread_pool: &ThreadPool,
641 ) -> EntryVerificationState {
642 let now = Instant::now();
643 let genesis = [Entry {
644 num_hashes: 0,
645 hash: *start_hash,
646 transactions: vec![],
647 }];
648 let entry_pairs = genesis.par_iter().chain(self).zip(self);
649 let res = thread_pool.install(|| {
650 entry_pairs.all(|(x0, x1)| {
651 let r = x1.verify(&x0.hash);
652 if !r {
653 warn!(
654 "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
655 x0.hash,
656 x1.hash,
657 x1.transactions.len()
658 );
659 }
660 r
661 })
662 });
663 let poh_duration_us = now.elapsed().as_micros() as u64;
664 EntryVerificationState {
665 verification_status: if res {
666 EntryVerificationStatus::Success
667 } else {
668 EntryVerificationStatus::Failure
669 },
670 poh_duration_us,
671 device_verification_data: DeviceVerificationData::Cpu(),
672 }
673 }
674
675 fn verify_cpu_x86_simd(
676 &self,
677 start_hash: &Hash,
678 simd_len: usize,
679 thread_pool: &ThreadPool,
680 ) -> EntryVerificationState {
681 use solana_hash::HASH_BYTES;
682 let now = Instant::now();
683 let genesis = [Entry {
684 num_hashes: 0,
685 hash: *start_hash,
686 transactions: vec![],
687 }];
688
689 let aligned_len = self.len().div_ceil(simd_len) * simd_len;
690 let mut hashes_bytes = vec![0u8; HASH_BYTES * aligned_len];
691 genesis
692 .iter()
693 .chain(self)
694 .enumerate()
695 .for_each(|(i, entry)| {
696 if i < self.len() {
697 let start = i * HASH_BYTES;
698 let end = start + HASH_BYTES;
699 hashes_bytes[start..end].copy_from_slice(&entry.hash.to_bytes());
700 }
701 });
702 let mut hashes_chunked: Vec<_> = hashes_bytes.chunks_mut(simd_len * HASH_BYTES).collect();
703
704 let mut num_hashes: Vec<u64> = self
705 .iter()
706 .map(|entry| entry.num_hashes.saturating_sub(1))
707 .collect();
708 num_hashes.resize(aligned_len, 0);
709 let num_hashes: Vec<_> = num_hashes.chunks(simd_len).collect();
710
711 let res = thread_pool.install(|| {
712 hashes_chunked
713 .par_iter_mut()
714 .zip(num_hashes)
715 .enumerate()
716 .all(|(i, (chunk, num_hashes))| {
717 match simd_len {
718 8 => unsafe {
719 (api().unwrap().poh_verify_many_simd_avx2)(
720 chunk.as_mut_ptr(),
721 num_hashes.as_ptr(),
722 );
723 },
724 16 => unsafe {
725 (api().unwrap().poh_verify_many_simd_avx512skx)(
726 chunk.as_mut_ptr(),
727 num_hashes.as_ptr(),
728 );
729 },
730 _ => {
731 panic!("unsupported simd len: {simd_len}");
732 }
733 }
734 let entry_start = i * simd_len;
735 let entry_end = std::cmp::min(entry_start + simd_len, self.len());
738 self[entry_start..entry_end]
739 .iter()
740 .enumerate()
741 .all(|(j, ref_entry)| {
742 let start = j * HASH_BYTES;
743 let end = start + HASH_BYTES;
744 let hash = <[u8; HASH_BYTES]>::try_from(&chunk[start..end])
745 .map(Hash::new_from_array)
746 .unwrap();
747 compare_hashes(hash, ref_entry)
748 })
749 })
750 });
751 let poh_duration_us = now.elapsed().as_micros() as u64;
752 EntryVerificationState {
753 verification_status: if res {
754 EntryVerificationStatus::Success
755 } else {
756 EntryVerificationStatus::Failure
757 },
758 poh_duration_us,
759 device_verification_data: DeviceVerificationData::Cpu(),
760 }
761 }
762
763 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
764 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
765 let (has_avx2, has_avx512) = (
766 is_x86_feature_detected!("avx2"),
767 is_x86_feature_detected!("avx512f"),
768 );
769 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
770 let (has_avx2, has_avx512) = (false, false);
771
772 if api().is_some() {
773 if has_avx512 && self.len() >= 128 {
774 self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
775 } else if has_avx2 && self.len() >= 48 {
776 self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
777 } else {
778 self.verify_cpu_generic(start_hash, thread_pool)
779 }
780 } else {
781 self.verify_cpu_generic(start_hash, thread_pool)
782 }
783 }
784
785 fn start_verify(
786 &self,
787 start_hash: &Hash,
788 thread_pool: &ThreadPool,
789 recyclers: VerifyRecyclers,
790 ) -> EntryVerificationState {
791 let start = Instant::now();
792 let Some(api) = perf_libs::api() else {
793 return self.verify_cpu(start_hash, thread_pool);
794 };
795 inc_new_counter_info!("entry_verify-num_entries", self.len());
796
797 let genesis = [Entry {
798 num_hashes: 0,
799 hash: *start_hash,
800 transactions: vec![],
801 }];
802
803 let hashes: Vec<Hash> = genesis
804 .iter()
805 .chain(self)
806 .map(|entry| entry.hash)
807 .take(self.len())
808 .collect();
809
810 let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
811 hashes_pinned.set_pinnable();
812 hashes_pinned.resize(hashes.len(), Hash::default());
813 hashes_pinned.copy_from_slice(&hashes);
814
815 let mut num_hashes_vec = recyclers
816 .tick_count_recycler
817 .allocate("poh_verify_num_hashes");
818 num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
819 for entry in self {
820 num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
821 }
822
823 let length = self.len();
824 let hashes = Arc::new(Mutex::new(hashes_pinned));
825 let hashes_clone = hashes.clone();
826
827 let gpu_verify_thread = thread::Builder::new()
828 .name("solGpuPohVerify".into())
829 .spawn(move || {
830 let mut hashes = hashes_clone.lock().unwrap();
831 let gpu_wait = Instant::now();
832 let res;
833 unsafe {
834 res = (api.poh_verify_many)(
835 hashes.as_mut_ptr() as *mut u8,
836 num_hashes_vec.as_ptr(),
837 length,
838 1,
839 );
840 }
841 assert!(res == 0, "GPU PoH verify many failed");
842 inc_new_counter_info!(
843 "entry_verify-gpu_thread",
844 gpu_wait.elapsed().as_micros() as usize
845 );
846 gpu_wait.elapsed().as_micros() as u64
847 })
848 .unwrap();
849
850 let verifications = thread_pool.install(|| {
851 self.into_par_iter()
852 .map(|entry| {
853 let answer = entry.hash;
854 let action = if entry.transactions.is_empty() {
855 if entry.num_hashes == 0 {
856 VerifyAction::None
857 } else {
858 VerifyAction::Tick
859 }
860 } else {
861 VerifyAction::Mixin(hash_transactions(&entry.transactions))
862 };
863 (action, answer)
864 })
865 .collect()
866 });
867 let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
868 thread_h: Some(gpu_verify_thread),
869 verifications: Some(verifications),
870 hashes: Some(hashes),
871 });
872 EntryVerificationState {
873 verification_status: EntryVerificationStatus::Pending,
874 poh_duration_us: start.elapsed().as_micros() as u64,
875 device_verification_data,
876 }
877 }
878
879 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
880 if hashes_per_tick == 0 {
882 return true;
883 }
884
885 for entry in self {
886 *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
887 if entry.is_tick() {
888 if *tick_hash_count != hashes_per_tick {
889 warn!(
890 "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}",
891 entry,
892 tick_hash_count,
893 hashes_per_tick
894 );
895 return false;
896 }
897 *tick_hash_count = 0;
898 }
899 }
900 *tick_hash_count < hashes_per_tick
901 }
902
903 fn tick_count(&self) -> u64 {
904 self.iter().filter(|e| e.is_tick()).count() as u64
905 }
906}
907
908pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
909 let entry = Entry::new(start, num_hashes, transactions);
910 *start = entry.hash;
911 entry
912}
913
914pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
915 repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
916 .take(num_ticks as usize)
917 .collect()
918}
919
920pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
921 repeat_with(|| {
922 let hashes_per_tick = thread_rng().gen_range(1..max_hashes_per_tick);
923 next_entry_mut(&mut hash, hashes_per_tick, vec![])
924 })
925 .take(num_ticks as usize)
926 .collect()
927}
928
929pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
931 let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
932 next_versioned_entry(prev_hash, num_hashes, transactions)
933}
934
935pub fn next_versioned_entry(
937 prev_hash: &Hash,
938 num_hashes: u64,
939 transactions: Vec<VersionedTransaction>,
940) -> Entry {
941 assert!(num_hashes > 0 || transactions.is_empty());
942 Entry {
943 num_hashes,
944 hash: next_hash(prev_hash, num_hashes, &transactions),
945 transactions,
946 }
947}
948
949pub fn thread_pool_for_tests() -> ThreadPool {
950 rayon::ThreadPoolBuilder::new()
955 .num_threads(4)
956 .thread_name(|i| format!("solEntryTest{i:02}"))
957 .build()
958 .expect("new rayon threadpool")
959}
960
961#[cfg(feature = "dev-context-only-utils")]
962pub fn thread_pool_for_benches() -> ThreadPool {
963 rayon::ThreadPoolBuilder::new()
964 .num_threads(num_cpus::get())
965 .thread_name(|i| format!("solEntryBnch{i:02}"))
966 .build()
967 .expect("new rayon threadpool")
968}
969
970#[cfg(test)]
971mod tests {
972 use {
973 super::*,
974 agave_reserved_account_keys::ReservedAccountKeys,
975 solana_hash::Hash,
976 solana_keypair::Keypair,
977 solana_message::SimpleAddressLoader,
978 solana_perf::test_tx::{test_invalid_tx, test_tx},
979 solana_pubkey::Pubkey,
980 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
981 solana_sha256_hasher::hash,
982 solana_signer::Signer,
983 solana_system_transaction as system_transaction,
984 solana_transaction::{
985 sanitized::{MessageHash, SanitizedTransaction},
986 versioned::VersionedTransaction,
987 },
988 solana_transaction_error::TransactionResult as Result,
989 };
990
991 #[test]
992 fn test_entry_verify() {
993 let zero = Hash::default();
994 let one = hash(zero.as_ref());
995 assert!(Entry::new_tick(0, &zero).verify(&zero)); assert!(!Entry::new_tick(0, &zero).verify(&one)); assert!(next_entry(&zero, 1, vec![]).verify(&zero)); assert!(!next_entry(&zero, 1, vec![]).verify(&one)); }
1000
1001 fn test_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
1002 entries: Vec<Entry>,
1003 skip_verification: bool,
1004 verify_recyclers: VerifyRecyclers,
1005 thread_pool: &ThreadPool,
1006 verify: Arc<
1007 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
1008 >,
1009 ) -> bool {
1010 let verify_func = {
1011 let verify = verify.clone();
1012 let verification_mode = if skip_verification {
1013 TransactionVerificationMode::HashOnly
1014 } else {
1015 TransactionVerificationMode::FullVerification
1016 };
1017 move |versioned_tx: VersionedTransaction| -> Result<Tx> {
1018 verify(versioned_tx, verification_mode)
1019 }
1020 };
1021
1022 let cpu_verify_result =
1023 verify_transactions(entries.clone(), thread_pool, Arc::new(verify_func));
1024 let mut gpu_verify_result: EntrySigVerificationState<Tx> = {
1025 let verify_result = start_verify_transactions(
1026 entries,
1027 skip_verification,
1028 thread_pool,
1029 verify_recyclers,
1030 verify,
1031 );
1032 match verify_result {
1033 Ok(res) => res,
1034 _ => EntrySigVerificationState {
1035 verification_status: EntryVerificationStatus::Failure,
1036 entries: None,
1037 device_verification_data: DeviceSigVerificationData::Cpu(),
1038 gpu_verify_duration_us: 0,
1039 },
1040 }
1041 };
1042
1043 match cpu_verify_result {
1044 Ok(_) => {
1045 assert!(gpu_verify_result.verification_status != EntryVerificationStatus::Failure);
1046 assert!(gpu_verify_result.finish_verify());
1047 true
1048 }
1049 _ => {
1050 assert!(
1051 gpu_verify_result.verification_status == EntryVerificationStatus::Failure
1052 || !gpu_verify_result.finish_verify()
1053 );
1054 false
1055 }
1056 }
1057 }
1058
1059 #[test]
1060 fn test_entry_gpu_verify() {
1061 let thread_pool = thread_pool_for_tests();
1062
1063 let verify_transaction = {
1064 move |versioned_tx: VersionedTransaction,
1065 verification_mode: TransactionVerificationMode|
1066 -> Result<RuntimeTransaction<SanitizedTransaction>> {
1067 let sanitized_tx = {
1068 let message_hash =
1069 if verification_mode == TransactionVerificationMode::FullVerification {
1070 versioned_tx.verify_and_hash_message()?
1071 } else {
1072 versioned_tx.message.hash()
1073 };
1074
1075 RuntimeTransaction::try_create(
1076 versioned_tx,
1077 MessageHash::Precomputed(message_hash),
1078 None,
1079 SimpleAddressLoader::Disabled,
1080 &ReservedAccountKeys::empty_key_set(),
1081 )
1082 }?;
1083
1084 Ok(sanitized_tx)
1085 }
1086 };
1087
1088 let recycler = VerifyRecyclers::default();
1089
1090 let entries_invalid = (0..1025)
1092 .map(|_| {
1093 let transaction = test_invalid_tx();
1094 next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1095 })
1096 .collect::<Vec<_>>();
1097
1098 let entries_valid = (0..1025)
1099 .map(|_| {
1100 let transaction = test_tx();
1101 next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1102 })
1103 .collect::<Vec<_>>();
1104
1105 assert!(!test_verify_transactions(
1106 entries_invalid,
1107 false,
1108 recycler.clone(),
1109 &thread_pool,
1110 Arc::new(verify_transaction)
1111 ));
1112 assert!(test_verify_transactions(
1113 entries_valid,
1114 false,
1115 recycler,
1116 &thread_pool,
1117 Arc::new(verify_transaction)
1118 ));
1119 }
1120
1121 #[test]
1122 fn test_transaction_reorder_attack() {
1123 let zero = Hash::default();
1124
1125 let keypair = Keypair::new();
1127 let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1128 let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1129 let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
1130 assert!(e0.verify(&zero));
1131
1132 e0.transactions[0] = tx1.into(); e0.transactions[1] = tx0.into();
1135 assert!(!e0.verify(&zero));
1136 }
1137
1138 #[test]
1139 fn test_transaction_signing() {
1140 let thread_pool = thread_pool_for_tests();
1141
1142 use solana_signature::Signature;
1143 let zero = Hash::default();
1144
1145 let keypair = Keypair::new();
1146 let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1147 let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1148
1149 let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
1151 assert!(e0.verify(&zero, &thread_pool));
1152
1153 let orig_sig = e0[0].transactions[0].signatures[0];
1155 e0[0].transactions[0].signatures[0] = Signature::default();
1156 assert!(!e0.verify(&zero, &thread_pool));
1157
1158 e0[0].transactions[0].signatures[0] = orig_sig;
1160 assert!(e0.verify(&zero, &thread_pool));
1161
1162 let len = e0[0].transactions[0].signatures.len();
1164 e0[0].transactions[0]
1165 .signatures
1166 .resize(len - 1, Signature::default());
1167 assert!(!e0.verify(&zero, &thread_pool));
1168
1169 let e0 = [Entry::new(&zero, 0, vec![])];
1171 assert!(e0.verify(&zero, &thread_pool));
1172 }
1173
1174 #[test]
1175 fn test_next_entry() {
1176 let zero = Hash::default();
1177 let tick = next_entry(&zero, 1, vec![]);
1178 assert_eq!(tick.num_hashes, 1);
1179 assert_ne!(tick.hash, zero);
1180
1181 let tick = next_entry(&zero, 0, vec![]);
1182 assert_eq!(tick.num_hashes, 0);
1183 assert_eq!(tick.hash, zero);
1184
1185 let keypair = Keypair::new();
1186 let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1187 let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1188 assert_eq!(entry0.num_hashes, 1);
1189 assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1190 }
1191
1192 #[test]
1193 #[should_panic]
1194 fn test_next_entry_panic() {
1195 let zero = Hash::default();
1196 let keypair = Keypair::new();
1197 let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1198 next_entry(&zero, 0, vec![tx]);
1199 }
1200
1201 #[test]
1202 fn test_verify_slice1() {
1203 solana_logger::setup();
1204 let thread_pool = thread_pool_for_tests();
1205
1206 let zero = Hash::default();
1207 let one = hash(zero.as_ref());
1208 assert!(vec![][..].verify(&zero, &thread_pool));
1210 assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero, &thread_pool));
1212 assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one, &thread_pool));
1214 assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero, &thread_pool));
1216
1217 let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1218 bad_ticks[1].hash = one;
1219 assert!(!bad_ticks.verify(&zero, &thread_pool));
1221 }
1222
1223 #[test]
1224 fn test_verify_slice_with_hashes1() {
1225 solana_logger::setup();
1226 let thread_pool = thread_pool_for_tests();
1227
1228 let zero = Hash::default();
1229 let one = hash(zero.as_ref());
1230 let two = hash(one.as_ref());
1231 assert!(vec![][..].verify(&one, &thread_pool));
1233 assert!(vec![Entry::new_tick(1, &two)][..].verify(&one, &thread_pool));
1235 assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two, &thread_pool));
1237
1238 let mut ticks = vec![next_entry(&one, 1, vec![])];
1239 ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1240 assert!(ticks.verify(&one, &thread_pool));
1242
1243 let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1244 bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1245 bad_ticks[1].hash = one;
1246 assert!(!bad_ticks.verify(&one, &thread_pool));
1248 }
1249
1250 #[test]
1251 fn test_verify_slice_with_hashes_and_transactions() {
1252 solana_logger::setup();
1253 let thread_pool = thread_pool_for_tests();
1254
1255 let zero = Hash::default();
1256 let one = hash(zero.as_ref());
1257 let two = hash(one.as_ref());
1258 let alice_keypair = Keypair::new();
1259 let bob_keypair = Keypair::new();
1260 let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1261 let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1262 assert!(vec![][..].verify(&one, &thread_pool));
1264 assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one, &thread_pool));
1266 assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two, &thread_pool));
1268
1269 let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1270 ticks.push(next_entry(
1271 &ticks.last().unwrap().hash,
1272 1,
1273 vec![tx1.clone()],
1274 ));
1275
1276 assert!(ticks.verify(&one, &thread_pool));
1278
1279 let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1280 bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1281 bad_ticks[1].hash = one;
1282 assert!(!bad_ticks.verify(&one, &thread_pool));
1284 }
1285
1286 #[test]
1287 fn test_verify_tick_hash_count() {
1288 let hashes_per_tick = 10;
1289 let tx = VersionedTransaction::default();
1290
1291 let no_hash_tx_entry = Entry {
1292 transactions: vec![tx.clone()],
1293 ..Entry::default()
1294 };
1295 let single_hash_tx_entry = Entry {
1296 transactions: vec![tx.clone()],
1297 num_hashes: 1,
1298 ..Entry::default()
1299 };
1300 let partial_tx_entry = Entry {
1301 num_hashes: hashes_per_tick - 1,
1302 transactions: vec![tx.clone()],
1303 ..Entry::default()
1304 };
1305 let full_tx_entry = Entry {
1306 num_hashes: hashes_per_tick,
1307 transactions: vec![tx.clone()],
1308 ..Entry::default()
1309 };
1310 let max_hash_tx_entry = Entry {
1311 transactions: vec![tx],
1312 num_hashes: u64::MAX,
1313 ..Entry::default()
1314 };
1315
1316 let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1317 let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1318 let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1319 let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1320 let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1321
1322 let mut tick_hash_count = 0;
1324 let mut entries = vec![];
1325 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1326 assert_eq!(tick_hash_count, 0);
1327
1328 tick_hash_count = hashes_per_tick;
1330 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1331 assert_eq!(tick_hash_count, hashes_per_tick);
1332 tick_hash_count = 0;
1333
1334 entries = vec![max_hash_tx_entry.clone()];
1336 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1337 assert_eq!(tick_hash_count, 0);
1338
1339 entries = vec![partial_tick_entry.clone()];
1341 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1342 assert_eq!(tick_hash_count, hashes_per_tick - 1);
1343 tick_hash_count = 0;
1344
1345 entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1347 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1348 assert_eq!(tick_hash_count, 0);
1349
1350 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1352 assert_eq!(tick_hash_count, hashes_per_tick);
1353 tick_hash_count = 0;
1354
1355 entries = vec![partial_tx_entry];
1357 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1358 assert_eq!(tick_hash_count, hashes_per_tick - 1);
1359 tick_hash_count = 0;
1360
1361 entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1363 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1364 assert_eq!(tick_hash_count, 0);
1365
1366 entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1368 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1369 assert_eq!(tick_hash_count, hashes_per_tick + 1);
1370 tick_hash_count = 0;
1371
1372 entries = vec![full_tx_entry];
1374 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1375 assert_eq!(tick_hash_count, hashes_per_tick);
1376 tick_hash_count = 0;
1377
1378 entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1380 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1381 assert_eq!(tick_hash_count, 0);
1382
1383 let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1385 .map(|_| single_hash_tx_entry.clone())
1386 .collect();
1387 entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1388 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1389 assert_eq!(tick_hash_count, 0);
1390
1391 entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1393 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1394 assert_eq!(tick_hash_count, u64::MAX);
1395 tick_hash_count = 0;
1396
1397 entries = vec![max_hash_tx_entry, full_tick_entry];
1399 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1400 assert_eq!(tick_hash_count, u64::MAX);
1401 }
1402
1403 #[test]
1404 fn test_poh_verify_fuzz() {
1405 solana_logger::setup();
1406 for _ in 0..100 {
1407 let mut time = Measure::start("ticks");
1408 let num_ticks = thread_rng().gen_range(1..100);
1409 info!("create {} ticks:", num_ticks);
1410 let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1411 time.stop();
1412
1413 let mut modified = false;
1414 if thread_rng().gen_ratio(1, 2) {
1415 modified = true;
1416 let modify_idx = thread_rng().gen_range(0..num_ticks) as usize;
1417 entries[modify_idx].hash = hash(&[1, 2, 3]);
1418 }
1419
1420 info!("done.. {}", time);
1421 let mut time = Measure::start("poh");
1422 let res = entries.verify(&Hash::default(), &thread_pool_for_tests());
1423 assert_eq!(res, !modified);
1424 time.stop();
1425 info!("{} {}", time, res);
1426 }
1427 }
1428
1429 #[test]
1430 fn test_hash_transactions() {
1431 let mut transactions: Vec<_> = [test_tx(), test_tx(), test_tx()]
1432 .into_iter()
1433 .map(VersionedTransaction::from)
1434 .collect();
1435
1436 let hash1 = hash_transactions(&transactions);
1439 transactions.swap(0, 1);
1440 let hash2 = hash_transactions(&transactions);
1441 assert_ne!(hash1, hash2);
1442 }
1443}