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, 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(
494 versioned_tx,
495 TransactionVerificationMode::HashAndVerifyPrecompiles,
496 )
497 }
498 };
499
500 let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
501
502 let transactions = entries
503 .iter()
504 .filter_map(|entry_type| match entry_type {
505 EntryType::Tick(_) => None,
506 EntryType::Transactions(transactions) => Some(transactions),
507 })
508 .flatten()
509 .collect::<Vec<_>>();
510
511 if transactions.is_empty() {
512 return Ok(EntrySigVerificationState {
513 verification_status: EntryVerificationStatus::Success,
514 entries: Some(entries),
515 device_verification_data: DeviceSigVerificationData::Cpu(),
516 gpu_verify_duration_us: 0,
517 });
518 }
519
520 let packet_batches = thread_pool.install(|| {
521 transactions
522 .par_chunks(PACKETS_PER_BATCH)
523 .map(|transaction_chunk| {
524 let num_transactions = transaction_chunk.len();
525 let mut packet_batch = PacketBatch::new_with_recycler(
526 &verify_recyclers.packet_recycler,
527 num_transactions,
528 "entry-sig-verify",
529 );
530 unsafe {
536 packet_batch.set_len(num_transactions);
537 }
538 let transaction_iter = transaction_chunk
539 .iter()
540 .map(|tx| tx.to_versioned_transaction());
541
542 let res = packet_batch
543 .iter_mut()
544 .zip(transaction_iter)
545 .all(|(packet, tx)| {
546 *packet.meta_mut() = Meta::default();
547 Packet::populate_packet(packet, None, &tx).is_ok()
548 });
549 if res {
550 Ok(packet_batch)
551 } else {
552 Err(TransactionError::SanitizeFailure)
553 }
554 })
555 .collect::<Result<Vec<_>>>()
556 });
557 let mut packet_batches = packet_batches?;
558
559 let tx_offset_recycler = verify_recyclers.tx_offset_recycler;
560 let out_recycler = verify_recyclers.out_recycler;
561 let num_packets = transactions.len();
562 let gpu_verify_thread = thread::Builder::new()
563 .name("solGpuSigVerify".into())
564 .spawn(move || {
565 let mut verify_time = Measure::start("sigverify");
566 sigverify::ed25519_verify(
567 &mut packet_batches,
568 &tx_offset_recycler,
569 &out_recycler,
570 false,
571 num_packets,
572 );
573 let verified = packet_batches
574 .iter()
575 .all(|batch| batch.iter().all(|p| !p.meta().discard()));
576 verify_time.stop();
577 (verified, verify_time.as_us())
578 })
579 .unwrap();
580
581 Ok(EntrySigVerificationState {
582 verification_status: EntryVerificationStatus::Pending,
583 entries: Some(entries),
584 device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData {
585 thread_h: Some(gpu_verify_thread),
586 }),
587 gpu_verify_duration_us: 0,
588 })
589}
590
591fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
592 let actual = if !ref_entry.transactions.is_empty() {
593 let tx_hash = hash_transactions(&ref_entry.transactions);
594 let mut poh = Poh::new(computed_hash, None);
595 poh.record(tx_hash).unwrap().hash
596 } else if ref_entry.num_hashes > 0 {
597 let mut poh = Poh::new(computed_hash, None);
598 poh.tick().unwrap().hash
599 } else {
600 computed_hash
601 };
602 actual == ref_entry.hash
603}
604
605pub trait EntrySlice {
607 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState;
609 fn verify_cpu_generic(
610 &self,
611 start_hash: &Hash,
612 thread_pool: &ThreadPool,
613 ) -> EntryVerificationState;
614 fn verify_cpu_x86_simd(
615 &self,
616 start_hash: &Hash,
617 simd_len: usize,
618 thread_pool: &ThreadPool,
619 ) -> EntryVerificationState;
620 fn start_verify(
621 &self,
622 start_hash: &Hash,
623 thread_pool: &ThreadPool,
624 recyclers: VerifyRecyclers,
625 ) -> EntryVerificationState;
626 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool;
627 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
631 fn tick_count(&self) -> u64;
633}
634
635impl EntrySlice for [Entry] {
636 fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool {
637 self.start_verify(start_hash, thread_pool, VerifyRecyclers::default())
638 .finish_verify(thread_pool)
639 }
640
641 fn verify_cpu_generic(
642 &self,
643 start_hash: &Hash,
644 thread_pool: &ThreadPool,
645 ) -> EntryVerificationState {
646 let now = Instant::now();
647 let genesis = [Entry {
648 num_hashes: 0,
649 hash: *start_hash,
650 transactions: vec![],
651 }];
652 let entry_pairs = genesis.par_iter().chain(self).zip(self);
653 let res = thread_pool.install(|| {
654 entry_pairs.all(|(x0, x1)| {
655 let r = x1.verify(&x0.hash);
656 if !r {
657 warn!(
658 "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
659 x0.hash,
660 x1.hash,
661 x1.transactions.len()
662 );
663 }
664 r
665 })
666 });
667 let poh_duration_us = now.elapsed().as_micros() as u64;
668 EntryVerificationState {
669 verification_status: if res {
670 EntryVerificationStatus::Success
671 } else {
672 EntryVerificationStatus::Failure
673 },
674 poh_duration_us,
675 device_verification_data: DeviceVerificationData::Cpu(),
676 }
677 }
678
679 fn verify_cpu_x86_simd(
680 &self,
681 start_hash: &Hash,
682 simd_len: usize,
683 thread_pool: &ThreadPool,
684 ) -> EntryVerificationState {
685 use solana_hash::HASH_BYTES;
686 let now = Instant::now();
687 let genesis = [Entry {
688 num_hashes: 0,
689 hash: *start_hash,
690 transactions: vec![],
691 }];
692
693 let aligned_len = self.len().div_ceil(simd_len) * simd_len;
694 let mut hashes_bytes = vec![0u8; HASH_BYTES * aligned_len];
695 genesis
696 .iter()
697 .chain(self)
698 .enumerate()
699 .for_each(|(i, entry)| {
700 if i < self.len() {
701 let start = i * HASH_BYTES;
702 let end = start + HASH_BYTES;
703 hashes_bytes[start..end].copy_from_slice(&entry.hash.to_bytes());
704 }
705 });
706 let mut hashes_chunked: Vec<_> = hashes_bytes.chunks_mut(simd_len * HASH_BYTES).collect();
707
708 let mut num_hashes: Vec<u64> = self
709 .iter()
710 .map(|entry| entry.num_hashes.saturating_sub(1))
711 .collect();
712 num_hashes.resize(aligned_len, 0);
713 let num_hashes: Vec<_> = num_hashes.chunks(simd_len).collect();
714
715 let res = thread_pool.install(|| {
716 hashes_chunked
717 .par_iter_mut()
718 .zip(num_hashes)
719 .enumerate()
720 .all(|(i, (chunk, num_hashes))| {
721 match simd_len {
722 8 => unsafe {
723 (api().unwrap().poh_verify_many_simd_avx2)(
724 chunk.as_mut_ptr(),
725 num_hashes.as_ptr(),
726 );
727 },
728 16 => unsafe {
729 (api().unwrap().poh_verify_many_simd_avx512skx)(
730 chunk.as_mut_ptr(),
731 num_hashes.as_ptr(),
732 );
733 },
734 _ => {
735 panic!("unsupported simd len: {simd_len}");
736 }
737 }
738 let entry_start = i * simd_len;
739 let entry_end = std::cmp::min(entry_start + simd_len, self.len());
742 self[entry_start..entry_end]
743 .iter()
744 .enumerate()
745 .all(|(j, ref_entry)| {
746 let start = j * HASH_BYTES;
747 let end = start + HASH_BYTES;
748 let hash = <[u8; HASH_BYTES]>::try_from(&chunk[start..end])
749 .map(Hash::new_from_array)
750 .unwrap();
751 compare_hashes(hash, ref_entry)
752 })
753 })
754 });
755 let poh_duration_us = now.elapsed().as_micros() as u64;
756 EntryVerificationState {
757 verification_status: if res {
758 EntryVerificationStatus::Success
759 } else {
760 EntryVerificationStatus::Failure
761 },
762 poh_duration_us,
763 device_verification_data: DeviceVerificationData::Cpu(),
764 }
765 }
766
767 fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
768 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
769 let (has_avx2, has_avx512) = (
770 is_x86_feature_detected!("avx2"),
771 is_x86_feature_detected!("avx512f"),
772 );
773 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
774 let (has_avx2, has_avx512) = (false, false);
775
776 if api().is_some() {
777 if has_avx512 && self.len() >= 128 {
778 self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
779 } else if has_avx2 && self.len() >= 48 {
780 self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
781 } else {
782 self.verify_cpu_generic(start_hash, thread_pool)
783 }
784 } else {
785 self.verify_cpu_generic(start_hash, thread_pool)
786 }
787 }
788
789 fn start_verify(
790 &self,
791 start_hash: &Hash,
792 thread_pool: &ThreadPool,
793 recyclers: VerifyRecyclers,
794 ) -> EntryVerificationState {
795 let start = Instant::now();
796 let Some(api) = perf_libs::api() else {
797 return self.verify_cpu(start_hash, thread_pool);
798 };
799 inc_new_counter_info!("entry_verify-num_entries", self.len());
800
801 let genesis = [Entry {
802 num_hashes: 0,
803 hash: *start_hash,
804 transactions: vec![],
805 }];
806
807 let hashes: Vec<Hash> = genesis
808 .iter()
809 .chain(self)
810 .map(|entry| entry.hash)
811 .take(self.len())
812 .collect();
813
814 let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
815 hashes_pinned.set_pinnable();
816 hashes_pinned.resize(hashes.len(), Hash::default());
817 hashes_pinned.copy_from_slice(&hashes);
818
819 let mut num_hashes_vec = recyclers
820 .tick_count_recycler
821 .allocate("poh_verify_num_hashes");
822 num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
823 for entry in self {
824 num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
825 }
826
827 let length = self.len();
828 let hashes = Arc::new(Mutex::new(hashes_pinned));
829 let hashes_clone = hashes.clone();
830
831 let gpu_verify_thread = thread::Builder::new()
832 .name("solGpuPohVerify".into())
833 .spawn(move || {
834 let mut hashes = hashes_clone.lock().unwrap();
835 let gpu_wait = Instant::now();
836 let res;
837 unsafe {
838 res = (api.poh_verify_many)(
839 hashes.as_mut_ptr() as *mut u8,
840 num_hashes_vec.as_ptr(),
841 length,
842 1,
843 );
844 }
845 assert!(res == 0, "GPU PoH verify many failed");
846 inc_new_counter_info!(
847 "entry_verify-gpu_thread",
848 gpu_wait.elapsed().as_micros() as usize
849 );
850 gpu_wait.elapsed().as_micros() as u64
851 })
852 .unwrap();
853
854 let verifications = thread_pool.install(|| {
855 self.into_par_iter()
856 .map(|entry| {
857 let answer = entry.hash;
858 let action = if entry.transactions.is_empty() {
859 if entry.num_hashes == 0 {
860 VerifyAction::None
861 } else {
862 VerifyAction::Tick
863 }
864 } else {
865 VerifyAction::Mixin(hash_transactions(&entry.transactions))
866 };
867 (action, answer)
868 })
869 .collect()
870 });
871 let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
872 thread_h: Some(gpu_verify_thread),
873 verifications: Some(verifications),
874 hashes: Some(hashes),
875 });
876 EntryVerificationState {
877 verification_status: EntryVerificationStatus::Pending,
878 poh_duration_us: start.elapsed().as_micros() as u64,
879 device_verification_data,
880 }
881 }
882
883 fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
884 if hashes_per_tick == 0 {
886 return true;
887 }
888
889 for entry in self {
890 *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
891 if entry.is_tick() {
892 if *tick_hash_count != hashes_per_tick {
893 warn!(
894 "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}",
895 entry,
896 tick_hash_count,
897 hashes_per_tick
898 );
899 return false;
900 }
901 *tick_hash_count = 0;
902 }
903 }
904 *tick_hash_count < hashes_per_tick
905 }
906
907 fn tick_count(&self) -> u64 {
908 self.iter().filter(|e| e.is_tick()).count() as u64
909 }
910}
911
912pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
913 let entry = Entry::new(start, num_hashes, transactions);
914 *start = entry.hash;
915 entry
916}
917
918pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
919 repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
920 .take(num_ticks as usize)
921 .collect()
922}
923
924pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
925 repeat_with(|| {
926 let hashes_per_tick = thread_rng().gen_range(1..max_hashes_per_tick);
927 next_entry_mut(&mut hash, hashes_per_tick, vec![])
928 })
929 .take(num_ticks as usize)
930 .collect()
931}
932
933pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
935 let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
936 next_versioned_entry(prev_hash, num_hashes, transactions)
937}
938
939pub fn next_versioned_entry(
941 prev_hash: &Hash,
942 num_hashes: u64,
943 transactions: Vec<VersionedTransaction>,
944) -> Entry {
945 assert!(num_hashes > 0 || transactions.is_empty());
946 Entry {
947 num_hashes,
948 hash: next_hash(prev_hash, num_hashes, &transactions),
949 transactions,
950 }
951}
952
953pub fn thread_pool_for_tests() -> ThreadPool {
954 rayon::ThreadPoolBuilder::new()
959 .num_threads(4)
960 .thread_name(|i| format!("solEntryTest{i:02}"))
961 .build()
962 .expect("new rayon threadpool")
963}
964
965pub fn thread_pool_for_benches() -> ThreadPool {
966 rayon::ThreadPoolBuilder::new()
967 .num_threads(get_max_thread_count())
968 .thread_name(|i| format!("solEntryBnch{i:02}"))
969 .build()
970 .expect("new rayon threadpool")
971}
972
973#[cfg(test)]
974mod tests {
975 use {
976 super::*,
977 solana_hash::Hash,
978 solana_keypair::Keypair,
979 solana_message::SimpleAddressLoader,
980 solana_perf::test_tx::{test_invalid_tx, test_tx},
981 solana_pubkey::Pubkey,
982 solana_reserved_account_keys::ReservedAccountKeys,
983 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
984 solana_sha256_hasher::hash,
985 solana_signer::Signer,
986 solana_system_transaction as system_transaction,
987 solana_transaction::{
988 sanitized::{MessageHash, SanitizedTransaction},
989 versioned::VersionedTransaction,
990 },
991 solana_transaction_error::TransactionResult as Result,
992 };
993
994 #[test]
995 fn test_entry_verify() {
996 let zero = Hash::default();
997 let one = hash(zero.as_ref());
998 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)); }
1003
1004 fn test_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
1005 entries: Vec<Entry>,
1006 skip_verification: bool,
1007 verify_recyclers: VerifyRecyclers,
1008 thread_pool: &ThreadPool,
1009 verify: Arc<
1010 dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
1011 >,
1012 ) -> bool {
1013 let verify_func = {
1014 let verify = verify.clone();
1015 let verification_mode = if skip_verification {
1016 TransactionVerificationMode::HashOnly
1017 } else {
1018 TransactionVerificationMode::FullVerification
1019 };
1020 move |versioned_tx: VersionedTransaction| -> Result<Tx> {
1021 verify(versioned_tx, verification_mode)
1022 }
1023 };
1024
1025 let cpu_verify_result =
1026 verify_transactions(entries.clone(), thread_pool, Arc::new(verify_func));
1027 let mut gpu_verify_result: EntrySigVerificationState<Tx> = {
1028 let verify_result = start_verify_transactions(
1029 entries,
1030 skip_verification,
1031 thread_pool,
1032 verify_recyclers,
1033 verify,
1034 );
1035 match verify_result {
1036 Ok(res) => res,
1037 _ => EntrySigVerificationState {
1038 verification_status: EntryVerificationStatus::Failure,
1039 entries: None,
1040 device_verification_data: DeviceSigVerificationData::Cpu(),
1041 gpu_verify_duration_us: 0,
1042 },
1043 }
1044 };
1045
1046 match cpu_verify_result {
1047 Ok(_) => {
1048 assert!(gpu_verify_result.verification_status != EntryVerificationStatus::Failure);
1049 assert!(gpu_verify_result.finish_verify());
1050 true
1051 }
1052 _ => {
1053 assert!(
1054 gpu_verify_result.verification_status == EntryVerificationStatus::Failure
1055 || !gpu_verify_result.finish_verify()
1056 );
1057 false
1058 }
1059 }
1060 }
1061
1062 #[test]
1063 fn test_entry_gpu_verify() {
1064 let thread_pool = thread_pool_for_tests();
1065
1066 let verify_transaction = {
1067 move |versioned_tx: VersionedTransaction,
1068 verification_mode: TransactionVerificationMode|
1069 -> Result<RuntimeTransaction<SanitizedTransaction>> {
1070 let sanitized_tx = {
1071 let message_hash =
1072 if verification_mode == TransactionVerificationMode::FullVerification {
1073 versioned_tx.verify_and_hash_message()?
1074 } else {
1075 versioned_tx.message.hash()
1076 };
1077
1078 RuntimeTransaction::try_create(
1079 versioned_tx,
1080 MessageHash::Precomputed(message_hash),
1081 None,
1082 SimpleAddressLoader::Disabled,
1083 &ReservedAccountKeys::empty_key_set(),
1084 )
1085 }?;
1086
1087 Ok(sanitized_tx)
1088 }
1089 };
1090
1091 let recycler = VerifyRecyclers::default();
1092
1093 let entries_invalid = (0..1025)
1095 .map(|_| {
1096 let transaction = test_invalid_tx();
1097 next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1098 })
1099 .collect::<Vec<_>>();
1100
1101 let entries_valid = (0..1025)
1102 .map(|_| {
1103 let transaction = test_tx();
1104 next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1105 })
1106 .collect::<Vec<_>>();
1107
1108 assert!(!test_verify_transactions(
1109 entries_invalid,
1110 false,
1111 recycler.clone(),
1112 &thread_pool,
1113 Arc::new(verify_transaction)
1114 ));
1115 assert!(test_verify_transactions(
1116 entries_valid,
1117 false,
1118 recycler,
1119 &thread_pool,
1120 Arc::new(verify_transaction)
1121 ));
1122 }
1123
1124 #[test]
1125 fn test_transaction_reorder_attack() {
1126 let zero = Hash::default();
1127
1128 let keypair = Keypair::new();
1130 let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1131 let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1132 let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
1133 assert!(e0.verify(&zero));
1134
1135 e0.transactions[0] = tx1.into(); e0.transactions[1] = tx0.into();
1138 assert!(!e0.verify(&zero));
1139 }
1140
1141 #[test]
1142 fn test_transaction_signing() {
1143 let thread_pool = thread_pool_for_tests();
1144
1145 use solana_signature::Signature;
1146 let zero = Hash::default();
1147
1148 let keypair = Keypair::new();
1149 let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1150 let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1151
1152 let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
1154 assert!(e0.verify(&zero, &thread_pool));
1155
1156 let orig_sig = e0[0].transactions[0].signatures[0];
1158 e0[0].transactions[0].signatures[0] = Signature::default();
1159 assert!(!e0.verify(&zero, &thread_pool));
1160
1161 e0[0].transactions[0].signatures[0] = orig_sig;
1163 assert!(e0.verify(&zero, &thread_pool));
1164
1165 let len = e0[0].transactions[0].signatures.len();
1167 e0[0].transactions[0]
1168 .signatures
1169 .resize(len - 1, Signature::default());
1170 assert!(!e0.verify(&zero, &thread_pool));
1171
1172 let e0 = [Entry::new(&zero, 0, vec![])];
1174 assert!(e0.verify(&zero, &thread_pool));
1175 }
1176
1177 #[test]
1178 fn test_next_entry() {
1179 let zero = Hash::default();
1180 let tick = next_entry(&zero, 1, vec![]);
1181 assert_eq!(tick.num_hashes, 1);
1182 assert_ne!(tick.hash, zero);
1183
1184 let tick = next_entry(&zero, 0, vec![]);
1185 assert_eq!(tick.num_hashes, 0);
1186 assert_eq!(tick.hash, zero);
1187
1188 let keypair = Keypair::new();
1189 let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1190 let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1191 assert_eq!(entry0.num_hashes, 1);
1192 assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1193 }
1194
1195 #[test]
1196 #[should_panic]
1197 fn test_next_entry_panic() {
1198 let zero = Hash::default();
1199 let keypair = Keypair::new();
1200 let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1201 next_entry(&zero, 0, vec![tx]);
1202 }
1203
1204 #[test]
1205 fn test_verify_slice1() {
1206 solana_logger::setup();
1207 let thread_pool = thread_pool_for_tests();
1208
1209 let zero = Hash::default();
1210 let one = hash(zero.as_ref());
1211 assert!(vec![][..].verify(&zero, &thread_pool));
1213 assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero, &thread_pool));
1215 assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one, &thread_pool));
1217 assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero, &thread_pool));
1219
1220 let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1221 bad_ticks[1].hash = one;
1222 assert!(!bad_ticks.verify(&zero, &thread_pool));
1224 }
1225
1226 #[test]
1227 fn test_verify_slice_with_hashes1() {
1228 solana_logger::setup();
1229 let thread_pool = thread_pool_for_tests();
1230
1231 let zero = Hash::default();
1232 let one = hash(zero.as_ref());
1233 let two = hash(one.as_ref());
1234 assert!(vec![][..].verify(&one, &thread_pool));
1236 assert!(vec![Entry::new_tick(1, &two)][..].verify(&one, &thread_pool));
1238 assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two, &thread_pool));
1240
1241 let mut ticks = vec![next_entry(&one, 1, vec![])];
1242 ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1243 assert!(ticks.verify(&one, &thread_pool));
1245
1246 let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1247 bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1248 bad_ticks[1].hash = one;
1249 assert!(!bad_ticks.verify(&one, &thread_pool));
1251 }
1252
1253 #[test]
1254 fn test_verify_slice_with_hashes_and_transactions() {
1255 solana_logger::setup();
1256 let thread_pool = thread_pool_for_tests();
1257
1258 let zero = Hash::default();
1259 let one = hash(zero.as_ref());
1260 let two = hash(one.as_ref());
1261 let alice_keypair = Keypair::new();
1262 let bob_keypair = Keypair::new();
1263 let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1264 let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1265 assert!(vec![][..].verify(&one, &thread_pool));
1267 assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one, &thread_pool));
1269 assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two, &thread_pool));
1271
1272 let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1273 ticks.push(next_entry(
1274 &ticks.last().unwrap().hash,
1275 1,
1276 vec![tx1.clone()],
1277 ));
1278
1279 assert!(ticks.verify(&one, &thread_pool));
1281
1282 let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1283 bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1284 bad_ticks[1].hash = one;
1285 assert!(!bad_ticks.verify(&one, &thread_pool));
1287 }
1288
1289 #[test]
1290 fn test_verify_tick_hash_count() {
1291 let hashes_per_tick = 10;
1292 let tx = VersionedTransaction::default();
1293
1294 let no_hash_tx_entry = Entry {
1295 transactions: vec![tx.clone()],
1296 ..Entry::default()
1297 };
1298 let single_hash_tx_entry = Entry {
1299 transactions: vec![tx.clone()],
1300 num_hashes: 1,
1301 ..Entry::default()
1302 };
1303 let partial_tx_entry = Entry {
1304 num_hashes: hashes_per_tick - 1,
1305 transactions: vec![tx.clone()],
1306 ..Entry::default()
1307 };
1308 let full_tx_entry = Entry {
1309 num_hashes: hashes_per_tick,
1310 transactions: vec![tx.clone()],
1311 ..Entry::default()
1312 };
1313 let max_hash_tx_entry = Entry {
1314 transactions: vec![tx],
1315 num_hashes: u64::MAX,
1316 ..Entry::default()
1317 };
1318
1319 let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1320 let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1321 let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1322 let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1323 let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1324
1325 let mut tick_hash_count = 0;
1327 let mut entries = vec![];
1328 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1329 assert_eq!(tick_hash_count, 0);
1330
1331 tick_hash_count = hashes_per_tick;
1333 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1334 assert_eq!(tick_hash_count, hashes_per_tick);
1335 tick_hash_count = 0;
1336
1337 entries = vec![max_hash_tx_entry.clone()];
1339 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1340 assert_eq!(tick_hash_count, 0);
1341
1342 entries = vec![partial_tick_entry.clone()];
1344 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1345 assert_eq!(tick_hash_count, hashes_per_tick - 1);
1346 tick_hash_count = 0;
1347
1348 entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1350 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1351 assert_eq!(tick_hash_count, 0);
1352
1353 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1355 assert_eq!(tick_hash_count, hashes_per_tick);
1356 tick_hash_count = 0;
1357
1358 entries = vec![partial_tx_entry];
1360 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1361 assert_eq!(tick_hash_count, hashes_per_tick - 1);
1362 tick_hash_count = 0;
1363
1364 entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1366 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1367 assert_eq!(tick_hash_count, 0);
1368
1369 entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1371 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1372 assert_eq!(tick_hash_count, hashes_per_tick + 1);
1373 tick_hash_count = 0;
1374
1375 entries = vec![full_tx_entry];
1377 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1378 assert_eq!(tick_hash_count, hashes_per_tick);
1379 tick_hash_count = 0;
1380
1381 entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1383 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1384 assert_eq!(tick_hash_count, 0);
1385
1386 let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1388 .map(|_| single_hash_tx_entry.clone())
1389 .collect();
1390 entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1391 assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1392 assert_eq!(tick_hash_count, 0);
1393
1394 entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1396 assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1397 assert_eq!(tick_hash_count, u64::MAX);
1398 tick_hash_count = 0;
1399
1400 entries = vec![max_hash_tx_entry, full_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 }
1405
1406 #[test]
1407 fn test_poh_verify_fuzz() {
1408 solana_logger::setup();
1409 for _ in 0..100 {
1410 let mut time = Measure::start("ticks");
1411 let num_ticks = thread_rng().gen_range(1..100);
1412 info!("create {} ticks:", num_ticks);
1413 let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1414 time.stop();
1415
1416 let mut modified = false;
1417 if thread_rng().gen_ratio(1, 2) {
1418 modified = true;
1419 let modify_idx = thread_rng().gen_range(0..num_ticks) as usize;
1420 entries[modify_idx].hash = hash(&[1, 2, 3]);
1421 }
1422
1423 info!("done.. {}", time);
1424 let mut time = Measure::start("poh");
1425 let res = entries.verify(&Hash::default(), &thread_pool_for_tests());
1426 assert_eq!(res, !modified);
1427 time.stop();
1428 info!("{} {}", time, res);
1429 }
1430 }
1431
1432 #[test]
1433 fn test_hash_transactions() {
1434 let mut transactions: Vec<_> = [test_tx(), test_tx(), test_tx()]
1435 .into_iter()
1436 .map(VersionedTransaction::from)
1437 .collect();
1438
1439 let hash1 = hash_transactions(&transactions);
1442 transactions.swap(0, 1);
1443 let hash2 = hash_transactions(&transactions);
1444 assert_ne!(hash1, hash2);
1445 }
1446}