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_rayon_threadlimit::get_max_thread_count,
26 solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
27 solana_transaction::{
28 versioned::VersionedTransaction, Transaction, TransactionVerificationMode,
29 },
30 solana_transaction_error::{TransactionError, TransactionResult as Result},
31 std::{
32 cmp,
33 ffi::OsStr,
34 iter::repeat_with,
35 sync::{Arc, Mutex, Once, OnceLock},
36 thread::{self, JoinHandle},
37 time::Instant,
38 },
39};
40
41pub type EntrySender = Sender<Vec<Entry>>;
42pub type EntryReceiver = Receiver<Vec<Entry>>;
43
44static API: OnceLock<Container<Api>> = OnceLock::new();
45
46pub fn init_poh() {
47 init(OsStr::new("libpoh-simd.so"));
48}
49
50fn init(name: &OsStr) {
51 static INIT_HOOK: Once = Once::new();
52
53 info!("Loading {:?}", name);
54 INIT_HOOK.call_once(|| {
55 let path;
56 let lib_name = if let Some(perf_libs_path) = solana_perf::perf_libs::locate_perf_libs() {
57 solana_perf::perf_libs::append_to_ld_library_path(
58 perf_libs_path.to_str().unwrap_or("").to_string(),
59 );
60 path = perf_libs_path.join(name);
61 path.as_os_str()
62 } else {
63 name
64 };
65
66 match unsafe { Container::load(lib_name) } {
67 Ok(api) => _ = API.set(api),
68 Err(err) => error!("Unable to load {lib_name:?}: {err}"),
69 }
70 })
71}
72
73pub fn api() -> Option<&'static Container<Api<'static>>> {
74 {
75 static INIT_HOOK: Once = Once::new();
76 INIT_HOOK.call_once(|| {
77 if std::env::var("TEST_PERF_LIBS").is_ok() {
78 init_poh()
79 }
80 });
81 }
82
83 API.get()
84}
85
86#[derive(SymBorApi)]
87pub struct Api<'a> {
88 pub poh_verify_many_simd_avx512skx:
89 Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
90 pub poh_verify_many_simd_avx2:
91 Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
92}
93
94#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)]
122pub struct Entry {
123 pub num_hashes: u64,
125
126 pub hash: Hash,
128
129 pub transactions: Vec<VersionedTransaction>,
133}
134
135pub struct EntrySummary {
136 pub num_hashes: u64,
137 pub hash: Hash,
138 pub num_transactions: u64,
139}
140
141impl From<&Entry> for EntrySummary {
142 fn from(entry: &Entry) -> Self {
143 Self {
144 num_hashes: entry.num_hashes,
145 hash: entry.hash,
146 num_transactions: entry.transactions.len() as u64,
147 }
148 }
149}
150
151pub enum EntryType<Tx: TransactionWithMeta> {
153 Transactions(Vec<Tx>),
154 Tick(Hash),
155}
156
157impl Entry {
158 pub fn new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec<Transaction>) -> Self {
160 if num_hashes == 0 && !transactions.is_empty() {
163 num_hashes = 1;
164 }
165
166 let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
167 let hash = next_hash(prev_hash, num_hashes, &transactions);
168 Entry {
169 num_hashes,
170 hash,
171 transactions,
172 }
173 }
174
175 pub fn new_mut(
176 start_hash: &mut Hash,
177 num_hashes: &mut u64,
178 transactions: Vec<Transaction>,
179 ) -> Self {
180 let entry = Self::new(start_hash, *num_hashes, transactions);
181 *start_hash = entry.hash;
182 *num_hashes = 0;
183
184 entry
185 }
186
187 #[cfg(test)]
188 pub fn new_tick(num_hashes: u64, hash: &Hash) -> Self {
189 Entry {
190 num_hashes,
191 hash: *hash,
192 transactions: vec![],
193 }
194 }
195
196 pub fn verify(&self, start_hash: &Hash) -> bool {
199 let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions);
200 if self.hash != ref_hash {
201 warn!(
202 "next_hash is invalid expected: {:?} actual: {:?}",
203 self.hash, ref_hash
204 );
205 return false;
206 }
207 true
208 }
209
210 pub fn is_tick(&self) -> bool {
211 self.transactions.is_empty()
212 }
213}
214
215pub fn hash_transactions(transactions: &[VersionedTransaction]) -> Hash {
216 let signatures: Vec<_> = transactions
218 .iter()
219 .flat_map(|tx| tx.signatures.iter())
220 .collect();
221 let merkle_tree = MerkleTree::new(&signatures);
222 if let Some(root_hash) = merkle_tree.get_root() {
223 *root_hash
224 } else {
225 Hash::default()
226 }
227}
228
229pub fn next_hash(
234 start_hash: &Hash,
235 num_hashes: u64,
236 transactions: &[VersionedTransaction],
237) -> Hash {
238 if num_hashes == 0 && transactions.is_empty() {
239 return *start_hash;
240 }
241
242 let mut poh = Poh::new(*start_hash, None);
243 poh.hash(num_hashes.saturating_sub(1));
244 if transactions.is_empty() {
245 poh.tick().unwrap().hash
246 } else {
247 poh.record(hash_transactions(transactions)).unwrap().hash
248 }
249}
250
251enum VerifyAction {
253 Mixin(Hash),
255 Tick,
257 None,
259}
260
261pub struct GpuVerificationData {
262 thread_h: Option<JoinHandle<u64>>,
263 hashes: Option<Arc<Mutex<PinnedVec<Hash>>>>,
264 verifications: Option<Vec<(VerifyAction, Hash)>>,
265}
266
267pub enum DeviceVerificationData {
268 Cpu(),
269 Gpu(GpuVerificationData),
270}
271
272pub struct EntryVerificationState {
273 verification_status: EntryVerificationStatus,
274 poh_duration_us: u64,
275 device_verification_data: DeviceVerificationData,
276}
277
278pub struct GpuSigVerificationData {
279 thread_h: Option<JoinHandle<(bool, u64)>>,
280}
281
282pub enum DeviceSigVerificationData {
283 Cpu(),
284 Gpu(GpuSigVerificationData),
285}
286
287pub struct EntrySigVerificationState<Tx: TransactionWithMeta> {
288 verification_status: EntryVerificationStatus,
289 entries: Option<Vec<EntryType<Tx>>>,
290 device_verification_data: DeviceSigVerificationData,
291 gpu_verify_duration_us: u64,
292}
293
294impl<Tx: TransactionWithMeta> EntrySigVerificationState<Tx> {
295 pub fn entries(&mut self) -> Option<Vec<EntryType<Tx>>> {
296 self.entries.take()
297 }
298 pub fn finish_verify(&mut self) -> bool {
299 match &mut self.device_verification_data {
300 DeviceSigVerificationData::Gpu(verification_state) => {
301 let (verified, gpu_time_us) =
302 verification_state.thread_h.take().unwrap().join().unwrap();
303 self.gpu_verify_duration_us = gpu_time_us;
304 self.verification_status = if verified {
305 EntryVerificationStatus::Success
306 } else {
307 EntryVerificationStatus::Failure
308 };
309 verified
310 }
311 DeviceSigVerificationData::Cpu() => {
312 self.verification_status == EntryVerificationStatus::Success
313 }
314 }
315 }
316 pub fn status(&self) -> EntryVerificationStatus {
317 self.verification_status
318 }
319 pub fn gpu_verify_duration(&self) -> u64 {
320 self.gpu_verify_duration_us
321 }
322}
323
324#[derive(Default, Clone)]
325pub struct VerifyRecyclers {
326 hash_recycler: Recycler<PinnedVec<Hash>>,
327 tick_count_recycler: Recycler<PinnedVec<u64>>,
328 packet_recycler: PacketBatchRecycler,
329 out_recycler: Recycler<PinnedVec<u8>>,
330 tx_offset_recycler: Recycler<sigverify::TxOffset>,
331}
332
333#[derive(PartialEq, Eq, Clone, Copy, Debug)]
334pub enum EntryVerificationStatus {
335 Failure,
336 Success,
337 Pending,
338}
339
340impl EntryVerificationState {
341 pub fn status(&self) -> EntryVerificationStatus {
342 self.verification_status
343 }
344
345 pub fn poh_duration_us(&self) -> u64 {
346 self.poh_duration_us
347 }
348
349 pub fn finish_verify(&mut self, thread_pool: &ThreadPool) -> bool {
350 match &mut self.device_verification_data {
351 DeviceVerificationData::Gpu(verification_state) => {
352 let gpu_time_us = verification_state.thread_h.take().unwrap().join().unwrap();
353
354 let mut verify_check_time = Measure::start("verify_check");
355 let hashes = verification_state.hashes.take().unwrap();
356 let hashes = Arc::try_unwrap(hashes)
357 .expect("unwrap Arc")
358 .into_inner()
359 .expect("into_inner");
360 let res = thread_pool.install(|| {
361 hashes
362 .into_par_iter()
363 .cloned()
364 .zip(verification_state.verifications.take().unwrap())
365 .all(|(hash, (action, expected))| {
366 let actual = match action {
367 VerifyAction::Mixin(mixin) => {
368 Poh::new(hash, None).record(mixin).unwrap().hash
369 }
370 VerifyAction::Tick => Poh::new(hash, None).tick().unwrap().hash,
371 VerifyAction::None => hash,
372 };
373 actual == expected
374 })
375 });
376 verify_check_time.stop();
377 self.poh_duration_us += gpu_time_us + verify_check_time.as_us();
378
379 self.verification_status = if res {
380 EntryVerificationStatus::Success
381 } else {
382 EntryVerificationStatus::Failure
383 };
384 res
385 }
386 DeviceVerificationData::Cpu() => {
387 self.verification_status == EntryVerificationStatus::Success
388 }
389 }
390 }
391}
392
393pub fn verify_transactions<Tx: TransactionWithMeta + Send + Sync>(
394 entries: Vec<Entry>,
395 thread_pool: &ThreadPool,
396 verify: Arc<dyn Fn(VersionedTransaction) -> Result<Tx> + Send + Sync>,
397) -> Result<Vec<EntryType<Tx>>> {
398 thread_pool.install(|| {
399 entries
400 .into_par_iter()
401 .map(|entry| {
402 if entry.transactions.is_empty() {
403 Ok(EntryType::Tick(entry.hash))
404 } else {
405 Ok(EntryType::Transactions(
406 entry
407 .transactions
408 .into_par_iter()
409 .map(verify.as_ref())
410 .collect::<Result<Vec<_>>>()?,
411 ))
412 }
413 })
414 .collect()
415 })
416}
417
418pub fn start_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
419 entries: Vec<Entry>,
420 skip_verification: bool,
421 thread_pool: &ThreadPool,
422 verify_recyclers: VerifyRecyclers,
423 verify: Arc<
424 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
425 >,
426) -> Result<EntrySigVerificationState<Tx>> {
427 let api = perf_libs::api();
428
429 let use_cpu = skip_verification
436 || api.is_none()
437 || entries
438 .iter()
439 .try_fold(0, |accum: usize, entry: &Entry| -> Option<usize> {
440 if accum.saturating_add(entry.transactions.len()) < 512 {
441 Some(accum.saturating_add(entry.transactions.len()))
442 } else {
443 None
444 }
445 })
446 .is_some();
447
448 if use_cpu {
449 start_verify_transactions_cpu(entries, skip_verification, thread_pool, verify)
450 } else {
451 start_verify_transactions_gpu(entries, verify_recyclers, thread_pool, verify)
452 }
453}
454
455fn start_verify_transactions_cpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
456 entries: Vec<Entry>,
457 skip_verification: bool,
458 thread_pool: &ThreadPool,
459 verify: Arc<
460 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
461 >,
462) -> Result<EntrySigVerificationState<Tx>> {
463 let verify_func = {
464 let mode = if skip_verification {
465 TransactionVerificationMode::HashOnly
466 } else {
467 TransactionVerificationMode::FullVerification
468 };
469
470 move |versioned_tx| verify(versioned_tx, mode)
471 };
472
473 let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
474
475 Ok(EntrySigVerificationState {
476 verification_status: EntryVerificationStatus::Success,
477 entries: Some(entries),
478 device_verification_data: DeviceSigVerificationData::Cpu(),
479 gpu_verify_duration_us: 0,
480 })
481}
482
483fn start_verify_transactions_gpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
484 entries: Vec<Entry>,
485 verify_recyclers: VerifyRecyclers,
486 thread_pool: &ThreadPool,
487 verify: Arc<
488 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
489 >,
490) -> Result<EntrySigVerificationState<Tx>> {
491 let verify_func = {
492 move |versioned_tx: VersionedTransaction| -> Result<Tx> {
493 verify(versioned_tx, TransactionVerificationMode::HashOnly)
494 }
495 };
496
497 let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
498
499 let transactions = entries
500 .iter()
501 .filter_map(|entry_type| match entry_type {
502 EntryType::Tick(_) => None,
503 EntryType::Transactions(transactions) => Some(transactions),
504 })
505 .flatten()
506 .collect::<Vec<_>>();
507
508 if transactions.is_empty() {
509 return Ok(EntrySigVerificationState {
510 verification_status: EntryVerificationStatus::Success,
511 entries: Some(entries),
512 device_verification_data: DeviceSigVerificationData::Cpu(),
513 gpu_verify_duration_us: 0,
514 });
515 }
516
517 let packet_batches = thread_pool.install(|| {
518 transactions
519 .par_chunks(PACKETS_PER_BATCH)
520 .map(|transaction_chunk| {
521 let num_transactions = transaction_chunk.len();
522 let mut packet_batch = PinnedPacketBatch::new_with_recycler(
523 &verify_recyclers.packet_recycler,
524 num_transactions,
525 "entry-sig-verify",
526 );
527 unsafe {
533 packet_batch.set_len(num_transactions);
534 }
535 let transaction_iter = transaction_chunk
536 .iter()
537 .map(|tx| tx.to_versioned_transaction());
538
539 let res = packet_batch
540 .iter_mut()
541 .zip(transaction_iter)
542 .all(|(packet, tx)| {
543 *packet.meta_mut() = Meta::default();
544 Packet::populate_packet(packet, None, &tx).is_ok()
545 });
546 if res {
547 Ok(PacketBatch::from(packet_batch))
548 } else {
549 Err(TransactionError::SanitizeFailure)
550 }
551 })
552 .collect::<Result<Vec<_>>>()
553 });
554 let mut packet_batches = packet_batches?;
555
556 let tx_offset_recycler = verify_recyclers.tx_offset_recycler;
557 let out_recycler = verify_recyclers.out_recycler;
558 let num_packets = transactions.len();
559 let gpu_verify_thread = thread::Builder::new()
560 .name("solGpuSigVerify".into())
561 .spawn(move || {
562 let mut verify_time = Measure::start("sigverify");
563 sigverify::ed25519_verify(
564 &mut packet_batches,
565 &tx_offset_recycler,
566 &out_recycler,
567 false,
568 num_packets,
569 );
570 let verified = packet_batches
571 .iter()
572 .all(|batch| batch.iter().all(|p| !p.meta().discard()));
573 verify_time.stop();
574 (verified, verify_time.as_us())
575 })
576 .unwrap();
577
578 Ok(EntrySigVerificationState {
579 verification_status: EntryVerificationStatus::Pending,
580 entries: Some(entries),
581 device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData {
582 thread_h: Some(gpu_verify_thread),
583 }),
584 gpu_verify_duration_us: 0,
585 })
586}
587
588fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
589 let actual = if !ref_entry.transactions.is_empty() {
590 let tx_hash = hash_transactions(&ref_entry.transactions);
591 let mut poh = Poh::new(computed_hash, None);
592 poh.record(tx_hash).unwrap().hash
593 } else if ref_entry.num_hashes > 0 {
594 let mut poh = Poh::new(computed_hash, None);
595 poh.tick().unwrap().hash
596 } else {
597 computed_hash
598 };
599 actual == ref_entry.hash
600}
601
602pub trait EntrySlice {
604 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState;
606 fn verify_cpu_generic(
607 &self,
608 start_hash: &Hash,
609 thread_pool: &ThreadPool,
610 ) -> EntryVerificationState;
611 fn verify_cpu_x86_simd(
612 &self,
613 start_hash: &Hash,
614 simd_len: usize,
615 thread_pool: &ThreadPool,
616 ) -> EntryVerificationState;
617 fn start_verify(
618 &self,
619 start_hash: &Hash,
620 thread_pool: &ThreadPool,
621 recyclers: VerifyRecyclers,
622 ) -> EntryVerificationState;
623 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool;
624 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
628 fn tick_count(&self) -> u64;
630}
631
632impl EntrySlice for [Entry] {
633 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool {
634 self.start_verify(start_hash, thread_pool, VerifyRecyclers::default())
635 .finish_verify(thread_pool)
636 }
637
638 fn verify_cpu_generic(
639 &self,
640 start_hash: &Hash,
641 thread_pool: &ThreadPool,
642 ) -> EntryVerificationState {
643 let now = Instant::now();
644 let genesis = [Entry {
645 num_hashes: 0,
646 hash: *start_hash,
647 transactions: vec![],
648 }];
649 let entry_pairs = genesis.par_iter().chain(self).zip(self);
650 let res = thread_pool.install(|| {
651 entry_pairs.all(|(x0, x1)| {
652 let r = x1.verify(&x0.hash);
653 if !r {
654 warn!(
655 "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
656 x0.hash,
657 x1.hash,
658 x1.transactions.len()
659 );
660 }
661 r
662 })
663 });
664 let poh_duration_us = now.elapsed().as_micros() as u64;
665 EntryVerificationState {
666 verification_status: if res {
667 EntryVerificationStatus::Success
668 } else {
669 EntryVerificationStatus::Failure
670 },
671 poh_duration_us,
672 device_verification_data: DeviceVerificationData::Cpu(),
673 }
674 }
675
676 fn verify_cpu_x86_simd(
677 &self,
678 start_hash: &Hash,
679 simd_len: usize,
680 thread_pool: &ThreadPool,
681 ) -> EntryVerificationState {
682 use solana_hash::HASH_BYTES;
683 let now = Instant::now();
684 let genesis = [Entry {
685 num_hashes: 0,
686 hash: *start_hash,
687 transactions: vec![],
688 }];
689
690 let aligned_len = self.len().div_ceil(simd_len) * simd_len;
691 let mut hashes_bytes = vec![0u8; HASH_BYTES * aligned_len];
692 genesis
693 .iter()
694 .chain(self)
695 .enumerate()
696 .for_each(|(i, entry)| {
697 if i < self.len() {
698 let start = i * HASH_BYTES;
699 let end = start + HASH_BYTES;
700 hashes_bytes[start..end].copy_from_slice(&entry.hash.to_bytes());
701 }
702 });
703 let mut hashes_chunked: Vec<_> = hashes_bytes.chunks_mut(simd_len * HASH_BYTES).collect();
704
705 let mut num_hashes: Vec<u64> = self
706 .iter()
707 .map(|entry| entry.num_hashes.saturating_sub(1))
708 .collect();
709 num_hashes.resize(aligned_len, 0);
710 let num_hashes: Vec<_> = num_hashes.chunks(simd_len).collect();
711
712 let res = thread_pool.install(|| {
713 hashes_chunked
714 .par_iter_mut()
715 .zip(num_hashes)
716 .enumerate()
717 .all(|(i, (chunk, num_hashes))| {
718 match simd_len {
719 8 => unsafe {
720 (api().unwrap().poh_verify_many_simd_avx2)(
721 chunk.as_mut_ptr(),
722 num_hashes.as_ptr(),
723 );
724 },
725 16 => unsafe {
726 (api().unwrap().poh_verify_many_simd_avx512skx)(
727 chunk.as_mut_ptr(),
728 num_hashes.as_ptr(),
729 );
730 },
731 _ => {
732 panic!("unsupported simd len: {simd_len}");
733 }
734 }
735 let entry_start = i * simd_len;
736 let entry_end = std::cmp::min(entry_start + simd_len, self.len());
739 self[entry_start..entry_end]
740 .iter()
741 .enumerate()
742 .all(|(j, ref_entry)| {
743 let start = j * HASH_BYTES;
744 let end = start + HASH_BYTES;
745 let hash = <[u8; HASH_BYTES]>::try_from(&chunk[start..end])
746 .map(Hash::new_from_array)
747 .unwrap();
748 compare_hashes(hash, ref_entry)
749 })
750 })
751 });
752 let poh_duration_us = now.elapsed().as_micros() as u64;
753 EntryVerificationState {
754 verification_status: if res {
755 EntryVerificationStatus::Success
756 } else {
757 EntryVerificationStatus::Failure
758 },
759 poh_duration_us,
760 device_verification_data: DeviceVerificationData::Cpu(),
761 }
762 }
763
764 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
765 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
766 let (has_avx2, has_avx512) = (
767 is_x86_feature_detected!("avx2"),
768 is_x86_feature_detected!("avx512f"),
769 );
770 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
771 let (has_avx2, has_avx512) = (false, false);
772
773 if api().is_some() {
774 if has_avx512 && self.len() >= 128 {
775 self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
776 } else if has_avx2 && self.len() >= 48 {
777 self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
778 } else {
779 self.verify_cpu_generic(start_hash, thread_pool)
780 }
781 } else {
782 self.verify_cpu_generic(start_hash, thread_pool)
783 }
784 }
785
786 fn start_verify(
787 &self,
788 start_hash: &Hash,
789 thread_pool: &ThreadPool,
790 recyclers: VerifyRecyclers,
791 ) -> EntryVerificationState {
792 let start = Instant::now();
793 let Some(api) = perf_libs::api() else {
794 return self.verify_cpu(start_hash, thread_pool);
795 };
796 inc_new_counter_info!("entry_verify-num_entries", self.len());
797
798 let genesis = [Entry {
799 num_hashes: 0,
800 hash: *start_hash,
801 transactions: vec![],
802 }];
803
804 let hashes: Vec<Hash> = genesis
805 .iter()
806 .chain(self)
807 .map(|entry| entry.hash)
808 .take(self.len())
809 .collect();
810
811 let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
812 hashes_pinned.set_pinnable();
813 hashes_pinned.resize(hashes.len(), Hash::default());
814 hashes_pinned.copy_from_slice(&hashes);
815
816 let mut num_hashes_vec = recyclers
817 .tick_count_recycler
818 .allocate("poh_verify_num_hashes");
819 num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
820 for entry in self {
821 num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
822 }
823
824 let length = self.len();
825 let hashes = Arc::new(Mutex::new(hashes_pinned));
826 let hashes_clone = hashes.clone();
827
828 let gpu_verify_thread = thread::Builder::new()
829 .name("solGpuPohVerify".into())
830 .spawn(move || {
831 let mut hashes = hashes_clone.lock().unwrap();
832 let gpu_wait = Instant::now();
833 let res;
834 unsafe {
835 res = (api.poh_verify_many)(
836 hashes.as_mut_ptr() as *mut u8,
837 num_hashes_vec.as_ptr(),
838 length,
839 1,
840 );
841 }
842 assert!(res == 0, "GPU PoH verify many failed");
843 inc_new_counter_info!(
844 "entry_verify-gpu_thread",
845 gpu_wait.elapsed().as_micros() as usize
846 );
847 gpu_wait.elapsed().as_micros() as u64
848 })
849 .unwrap();
850
851 let verifications = thread_pool.install(|| {
852 self.into_par_iter()
853 .map(|entry| {
854 let answer = entry.hash;
855 let action = if entry.transactions.is_empty() {
856 if entry.num_hashes == 0 {
857 VerifyAction::None
858 } else {
859 VerifyAction::Tick
860 }
861 } else {
862 VerifyAction::Mixin(hash_transactions(&entry.transactions))
863 };
864 (action, answer)
865 })
866 .collect()
867 });
868 let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
869 thread_h: Some(gpu_verify_thread),
870 verifications: Some(verifications),
871 hashes: Some(hashes),
872 });
873 EntryVerificationState {
874 verification_status: EntryVerificationStatus::Pending,
875 poh_duration_us: start.elapsed().as_micros() as u64,
876 device_verification_data,
877 }
878 }
879
880 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
881 if hashes_per_tick == 0 {
883 return true;
884 }
885
886 for entry in self {
887 *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
888 if entry.is_tick() {
889 if *tick_hash_count != hashes_per_tick {
890 warn!(
891 "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}",
892 entry,
893 tick_hash_count,
894 hashes_per_tick
895 );
896 return false;
897 }
898 *tick_hash_count = 0;
899 }
900 }
901 *tick_hash_count < hashes_per_tick
902 }
903
904 fn tick_count(&self) -> u64 {
905 self.iter().filter(|e| e.is_tick()).count() as u64
906 }
907}
908
909pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
910 let entry = Entry::new(start, num_hashes, transactions);
911 *start = entry.hash;
912 entry
913}
914
915pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
916 repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
917 .take(num_ticks as usize)
918 .collect()
919}
920
921pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
922 repeat_with(|| {
923 let hashes_per_tick = thread_rng().gen_range(1..max_hashes_per_tick);
924 next_entry_mut(&mut hash, hashes_per_tick, vec![])
925 })
926 .take(num_ticks as usize)
927 .collect()
928}
929
930pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
932 let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
933 next_versioned_entry(prev_hash, num_hashes, transactions)
934}
935
936pub fn next_versioned_entry(
938 prev_hash: &Hash,
939 num_hashes: u64,
940 transactions: Vec<VersionedTransaction>,
941) -> Entry {
942 assert!(num_hashes > 0 || transactions.is_empty());
943 Entry {
944 num_hashes,
945 hash: next_hash(prev_hash, num_hashes, &transactions),
946 transactions,
947 }
948}
949
950pub fn thread_pool_for_tests() -> ThreadPool {
951 rayon::ThreadPoolBuilder::new()
956 .num_threads(4)
957 .thread_name(|i| format!("solEntryTest{i:02}"))
958 .build()
959 .expect("new rayon threadpool")
960}
961
962pub fn thread_pool_for_benches() -> ThreadPool {
963 rayon::ThreadPoolBuilder::new()
964 .num_threads(get_max_thread_count())
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}