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