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