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