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