solana_entry/
entry.rs

1//! The `entry` module is a fundamental building block of Proof of History. It contains a
2//! unique ID that is the hash of the Entry before it, plus the hash of the
3//! transactions within it. Entries cannot be reordered, and its field `num_hashes`
4//! represents an approximate amount of time since the last Entry was created.
5use {
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_measure::measure::Measure,
14    solana_merkle_tree::MerkleTree,
15    solana_metrics::*,
16    solana_perf::{
17        cuda_runtime::PinnedVec,
18        packet::{Packet, PacketBatch, PacketBatchRecycler, PACKETS_PER_BATCH},
19        perf_libs,
20        recycler::Recycler,
21        sigverify,
22    },
23    solana_rayon_threadlimit::get_max_thread_count,
24    solana_sdk::{
25        hash::Hash,
26        packet::Meta,
27        transaction::{
28            Result, SanitizedTransaction, Transaction, TransactionError,
29            TransactionVerificationMode, VersionedTransaction,
30        },
31    },
32    std::{
33        cmp,
34        ffi::OsStr,
35        iter::repeat_with,
36        sync::{Arc, Mutex, Once},
37        thread::{self, JoinHandle},
38        time::Instant,
39    },
40};
41
42pub type EntrySender = Sender<Vec<Entry>>;
43pub type EntryReceiver = Receiver<Vec<Entry>>;
44
45static mut API: Option<Container<Api>> = None;
46
47pub fn init_poh() {
48    init(OsStr::new("libpoh-simd.so"));
49}
50
51fn init(name: &OsStr) {
52    static INIT_HOOK: Once = Once::new();
53
54    info!("Loading {:?}", name);
55    unsafe {
56        INIT_HOOK.call_once(|| {
57            let path;
58            let lib_name = if let Some(perf_libs_path) = solana_perf::perf_libs::locate_perf_libs()
59            {
60                solana_perf::perf_libs::append_to_ld_library_path(
61                    perf_libs_path.to_str().unwrap_or("").to_string(),
62                );
63                path = perf_libs_path.join(name);
64                path.as_os_str()
65            } else {
66                name
67            };
68
69            API = Container::load(lib_name).ok();
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    unsafe { API.as_ref() }
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/// Each Entry contains three pieces of data. The `num_hashes` field is the number
96/// of hashes performed since the previous entry.  The `hash` field is the result
97/// of hashing `hash` from the previous entry `num_hashes` times.  The `transactions`
98/// field points to Transactions that took place shortly before `hash` was generated.
99///
100/// If you multiply `num_hashes` by the amount of time it takes to generate a new hash, you
101/// get a duration estimate since the last `Entry`. Since processing power increases
102/// over time, one should expect the duration `num_hashes` represents to decrease proportionally.
103/// An upper bound on Duration can be estimated by assuming each hash was generated by the
104/// world's fastest processor at the time the entry was recorded. Or said another way, it
105/// is physically not possible for a shorter duration to have occurred if one assumes the
106/// hash was computed by the world's fastest processor at that time. The hash chain is both
107/// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of
108/// Work consensus!)
109///
110/// The solana core protocol currently requires an `Entry` to contain `transactions` that are
111/// executable in parallel. Implemented in:
112///
113/// * For TPU: `solana_core::banking_stage::BankingStage::process_and_record_transactions()`
114/// * For TVU: `solana_core::replay_stage::ReplayStage::replay_blockstore_into_bank()`
115///
116/// All transactions in the `transactions` field have to follow the read/write locking restrictions
117/// with regard to the accounts they reference. A single account can be either written by a single
118/// transaction, or read by one or more transactions, but not both.
119///
120/// This enforcement is done via a call to `solana_runtime::accounts::Accounts::lock_accounts()`
121/// with the `txs` argument holding all the `transactions` in the `Entry`.
122#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)]
123pub struct Entry {
124    /// The number of hashes since the previous Entry ID.
125    pub num_hashes: u64,
126
127    /// The SHA-256 hash `num_hashes` after the previous Entry ID.
128    pub hash: Hash,
129
130    /// An unordered list of transactions that were observed before the Entry ID was
131    /// generated. They may have been observed before a previous Entry ID but were
132    /// pushed back into this list to ensure deterministic interpretation of the ledger.
133    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
152/// Typed entry to distinguish between transaction and tick entries
153pub enum EntryType {
154    Transactions(Vec<SanitizedTransaction>),
155    Tick(Hash),
156}
157
158impl Entry {
159    /// Creates the next Entry `num_hashes` after `start_hash`.
160    pub fn new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec<Transaction>) -> Self {
161        // If you passed in transactions, but passed in num_hashes == 0, then
162        // next_hash will generate the next hash and set num_hashes == 1
163        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    /// Verifies self.hash is the result of hashing a `start_hash` `self.num_hashes` times.
198    /// If the transaction is not a Tick, then hash that as well.
199    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    // a hash of a slice of transactions only needs to hash the signatures
218    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
230/// Creates the hash `num_hashes` after `start_hash`. If the transaction contains
231/// a signature, the final hash will be a hash of both the previous ID and
232/// the signature.  If num_hashes is zero and there's no transaction data,
233///  start_hash is returned.
234pub 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
252/// Last action required to verify an entry
253enum VerifyAction {
254    /// Mixin a hash before computing the last hash for a transaction entry
255    Mixin(Hash),
256    /// Compute one last hash for a tick entry
257    Tick,
258    /// No action needed (tick entry with no hashes)
259    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 {
289    verification_status: EntryVerificationStatus,
290    entries: Option<Vec<EntryType>>,
291    device_verification_data: DeviceSigVerificationData,
292    gpu_verify_duration_us: u64,
293}
294
295impl EntrySigVerificationState {
296    pub fn entries(&mut self) -> Option<Vec<EntryType>> {
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(
395    entries: Vec<Entry>,
396    thread_pool: &ThreadPool,
397    verify: Arc<dyn Fn(VersionedTransaction) -> Result<SanitizedTransaction> + Send + Sync>,
398) -> Result<Vec<EntryType>> {
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(
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<SanitizedTransaction>
426            + Send
427            + Sync,
428    >,
429) -> Result<EntrySigVerificationState> {
430    let api = perf_libs::api();
431
432    // Use the CPU if we have too few transactions for GPU signature verification to be worth it.
433    // We will also use the CPU if no acceleration API is used or if we're skipping
434    // the signature verification as we'd have nothing to do on the GPU in that case.
435    // TODO: make the CPU-to GPU crossover point dynamic, perhaps based on similar future
436    // heuristics to what might be used in sigverify::ed25519_verify when a dynamic crossover
437    // is introduced for that function (see TODO in sigverify::ed25519_verify)
438    let use_cpu = skip_verification
439        || api.is_none()
440        || entries
441            .iter()
442            .try_fold(0, |accum: usize, entry: &Entry| -> Option<usize> {
443                if accum.saturating_add(entry.transactions.len()) < 512 {
444                    Some(accum.saturating_add(entry.transactions.len()))
445                } else {
446                    None
447                }
448            })
449            .is_some();
450
451    if use_cpu {
452        start_verify_transactions_cpu(entries, skip_verification, thread_pool, verify)
453    } else {
454        start_verify_transactions_gpu(entries, verify_recyclers, thread_pool, verify)
455    }
456}
457
458fn start_verify_transactions_cpu(
459    entries: Vec<Entry>,
460    skip_verification: bool,
461    thread_pool: &ThreadPool,
462    verify: Arc<
463        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<SanitizedTransaction>
464            + Send
465            + Sync,
466    >,
467) -> Result<EntrySigVerificationState> {
468    let verify_func = {
469        let mode = if skip_verification {
470            TransactionVerificationMode::HashOnly
471        } else {
472            TransactionVerificationMode::FullVerification
473        };
474
475        move |versioned_tx| verify(versioned_tx, mode)
476    };
477
478    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
479
480    Ok(EntrySigVerificationState {
481        verification_status: EntryVerificationStatus::Success,
482        entries: Some(entries),
483        device_verification_data: DeviceSigVerificationData::Cpu(),
484        gpu_verify_duration_us: 0,
485    })
486}
487
488fn start_verify_transactions_gpu(
489    entries: Vec<Entry>,
490    verify_recyclers: VerifyRecyclers,
491    thread_pool: &ThreadPool,
492    verify: Arc<
493        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<SanitizedTransaction>
494            + Send
495            + Sync,
496    >,
497) -> Result<EntrySigVerificationState> {
498    let verify_func = {
499        move |versioned_tx: VersionedTransaction| -> Result<SanitizedTransaction> {
500            verify(
501                versioned_tx,
502                TransactionVerificationMode::HashAndVerifyPrecompiles,
503            )
504        }
505    };
506
507    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
508
509    let transactions: Vec<&SanitizedTransaction> = 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 = PacketBatch::new_with_recycler(
533                    &verify_recyclers.packet_recycler,
534                    num_transactions,
535                    "entry-sig-verify",
536                );
537                // We use set_len here instead of resize(num_txs, Packet::default()), to save
538                // memory bandwidth and avoid writing a large amount of data that will be overwritten
539                // soon afterwards. As well, Packet::default() actually leaves the packet data
540                // uninitialized, so the initialization would simply write junk into
541                // the vector anyway.
542                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(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
612// an EntrySlice is a slice of Entries
613pub trait EntrySlice {
614    /// Verifies the hashes and counts of a slice of transactions are all consistent.
615    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    /// Checks that each entry tick has the correct number of hashes. Entry slices do not
635    /// necessarily end in a tick, so `tick_hash_count` is used to carry over the hash count
636    /// for the next entry slice.
637    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
638    /// Counts tick entries
639    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_sdk::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() + simd_len - 1) / 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                    // The last chunk may produce indexes larger than what we have in the reference entries
747                    // because it is aligned to simd_len.
748                    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 = Hash::new(&chunk[start..end]);
756                            compare_hashes(hash, ref_entry)
757                        })
758                })
759        });
760        let poh_duration_us = now.elapsed().as_micros() as u64;
761        EntryVerificationState {
762            verification_status: if res {
763                EntryVerificationStatus::Success
764            } else {
765                EntryVerificationStatus::Failure
766            },
767            poh_duration_us,
768            device_verification_data: DeviceVerificationData::Cpu(),
769        }
770    }
771
772    fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
773        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
774        let (has_avx2, has_avx512) = (
775            is_x86_feature_detected!("avx2"),
776            is_x86_feature_detected!("avx512f"),
777        );
778        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
779        let (has_avx2, has_avx512) = (false, false);
780
781        if api().is_some() {
782            if has_avx512 && self.len() >= 128 {
783                self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
784            } else if has_avx2 && self.len() >= 48 {
785                self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
786            } else {
787                self.verify_cpu_generic(start_hash, thread_pool)
788            }
789        } else {
790            self.verify_cpu_generic(start_hash, thread_pool)
791        }
792    }
793
794    fn start_verify(
795        &self,
796        start_hash: &Hash,
797        thread_pool: &ThreadPool,
798        recyclers: VerifyRecyclers,
799    ) -> EntryVerificationState {
800        let start = Instant::now();
801        let Some(api) = perf_libs::api() else {
802            return self.verify_cpu(start_hash, thread_pool);
803        };
804        inc_new_counter_info!("entry_verify-num_entries", self.len());
805
806        let genesis = [Entry {
807            num_hashes: 0,
808            hash: *start_hash,
809            transactions: vec![],
810        }];
811
812        let hashes: Vec<Hash> = genesis
813            .iter()
814            .chain(self)
815            .map(|entry| entry.hash)
816            .take(self.len())
817            .collect();
818
819        let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
820        hashes_pinned.set_pinnable();
821        hashes_pinned.resize(hashes.len(), Hash::default());
822        hashes_pinned.copy_from_slice(&hashes);
823
824        let mut num_hashes_vec = recyclers
825            .tick_count_recycler
826            .allocate("poh_verify_num_hashes");
827        num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
828        for entry in self {
829            num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
830        }
831
832        let length = self.len();
833        let hashes = Arc::new(Mutex::new(hashes_pinned));
834        let hashes_clone = hashes.clone();
835
836        let gpu_verify_thread = thread::Builder::new()
837            .name("solGpuPohVerify".into())
838            .spawn(move || {
839                let mut hashes = hashes_clone.lock().unwrap();
840                let gpu_wait = Instant::now();
841                let res;
842                unsafe {
843                    res = (api.poh_verify_many)(
844                        hashes.as_mut_ptr() as *mut u8,
845                        num_hashes_vec.as_ptr(),
846                        length,
847                        1,
848                    );
849                }
850                assert!(res == 0, "GPU PoH verify many failed");
851                inc_new_counter_info!(
852                    "entry_verify-gpu_thread",
853                    gpu_wait.elapsed().as_micros() as usize
854                );
855                gpu_wait.elapsed().as_micros() as u64
856            })
857            .unwrap();
858
859        let verifications = thread_pool.install(|| {
860            self.into_par_iter()
861                .map(|entry| {
862                    let answer = entry.hash;
863                    let action = if entry.transactions.is_empty() {
864                        if entry.num_hashes == 0 {
865                            VerifyAction::None
866                        } else {
867                            VerifyAction::Tick
868                        }
869                    } else {
870                        VerifyAction::Mixin(hash_transactions(&entry.transactions))
871                    };
872                    (action, answer)
873                })
874                .collect()
875        });
876        let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
877            thread_h: Some(gpu_verify_thread),
878            verifications: Some(verifications),
879            hashes: Some(hashes),
880        });
881        EntryVerificationState {
882            verification_status: EntryVerificationStatus::Pending,
883            poh_duration_us: start.elapsed().as_micros() as u64,
884            device_verification_data,
885        }
886    }
887
888    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
889        // When hashes_per_tick is 0, hashing is disabled.
890        if hashes_per_tick == 0 {
891            return true;
892        }
893
894        for entry in self {
895            *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
896            if entry.is_tick() {
897                if *tick_hash_count != hashes_per_tick {
898                    warn!(
899                        "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}",
900                        entry,
901                        tick_hash_count,
902                        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
938/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
939pub 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
944/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
945pub 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    // Allocate fewer threads for unit tests
960    // Unit tests typically aren't creating massive blocks to verify, and
961    // multiple tests could be running in parallel so any further parallelism
962    // will do more harm than good
963    rayon::ThreadPoolBuilder::new()
964        .num_threads(4)
965        .thread_name(|i| format!("solEntryTest{i:02}"))
966        .build()
967        .expect("new rayon threadpool")
968}
969
970pub fn thread_pool_for_benches() -> ThreadPool {
971    rayon::ThreadPoolBuilder::new()
972        .num_threads(get_max_thread_count())
973        .thread_name(|i| format!("solEntryBnch{i:02}"))
974        .build()
975        .expect("new rayon threadpool")
976}
977
978#[cfg(test)]
979mod tests {
980    use {
981        super::*,
982        solana_perf::test_tx::{test_invalid_tx, test_tx},
983        solana_sdk::{
984            hash::{hash, Hash},
985            pubkey::Pubkey,
986            reserved_account_keys::ReservedAccountKeys,
987            signature::{Keypair, Signer},
988            system_transaction,
989            transaction::{
990                Result, SanitizedTransaction, SimpleAddressLoader, VersionedTransaction,
991            },
992        },
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)); // base case, never used
1000        assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
1001        assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step
1002        assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad
1003    }
1004
1005    fn test_verify_transactions(
1006        entries: Vec<Entry>,
1007        skip_verification: bool,
1008        verify_recyclers: VerifyRecyclers,
1009        thread_pool: &ThreadPool,
1010        verify: Arc<
1011            dyn Fn(
1012                    VersionedTransaction,
1013                    TransactionVerificationMode,
1014                ) -> Result<SanitizedTransaction>
1015                + Send
1016                + 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<SanitizedTransaction> {
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 = {
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<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                    SanitizedTransaction::try_create(
1085                        versioned_tx,
1086                        message_hash,
1087                        None,
1088                        SimpleAddressLoader::Disabled,
1089                        &ReservedAccountKeys::empty_key_set(),
1090                    )
1091                }?;
1092
1093                Ok(sanitized_tx)
1094            }
1095        };
1096
1097        let recycler = VerifyRecyclers::default();
1098
1099        // Make sure we test with a number of transactions that's not a multiple of PACKETS_PER_BATCH
1100        let entries_invalid = (0..1025)
1101            .map(|_| {
1102                let transaction = test_invalid_tx();
1103                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1104            })
1105            .collect::<Vec<_>>();
1106
1107        let entries_valid = (0..1025)
1108            .map(|_| {
1109                let transaction = test_tx();
1110                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1111            })
1112            .collect::<Vec<_>>();
1113
1114        assert!(!test_verify_transactions(
1115            entries_invalid,
1116            false,
1117            recycler.clone(),
1118            &thread_pool,
1119            Arc::new(verify_transaction)
1120        ));
1121        assert!(test_verify_transactions(
1122            entries_valid,
1123            false,
1124            recycler,
1125            &thread_pool,
1126            Arc::new(verify_transaction)
1127        ));
1128    }
1129
1130    #[test]
1131    fn test_transaction_reorder_attack() {
1132        let zero = Hash::default();
1133
1134        // First, verify entries
1135        let keypair = Keypair::new();
1136        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1137        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1138        let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
1139        assert!(e0.verify(&zero));
1140
1141        // Next, swap two transactions and ensure verification fails.
1142        e0.transactions[0] = tx1.into(); // <-- attack
1143        e0.transactions[1] = tx0.into();
1144        assert!(!e0.verify(&zero));
1145    }
1146
1147    #[test]
1148    fn test_transaction_signing() {
1149        let thread_pool = thread_pool_for_tests();
1150
1151        use solana_sdk::signature::Signature;
1152        let zero = Hash::default();
1153
1154        let keypair = Keypair::new();
1155        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1156        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1157
1158        // Verify entry with 2 transactions
1159        let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
1160        assert!(e0.verify(&zero, &thread_pool));
1161
1162        // Clear signature of the first transaction, see that it does not verify
1163        let orig_sig = e0[0].transactions[0].signatures[0];
1164        e0[0].transactions[0].signatures[0] = Signature::default();
1165        assert!(!e0.verify(&zero, &thread_pool));
1166
1167        // restore original signature
1168        e0[0].transactions[0].signatures[0] = orig_sig;
1169        assert!(e0.verify(&zero, &thread_pool));
1170
1171        // Resize signatures and see verification fails.
1172        let len = e0[0].transactions[0].signatures.len();
1173        e0[0].transactions[0]
1174            .signatures
1175            .resize(len - 1, Signature::default());
1176        assert!(!e0.verify(&zero, &thread_pool));
1177
1178        // Pass an entry with no transactions
1179        let e0 = [Entry::new(&zero, 0, vec![])];
1180        assert!(e0.verify(&zero, &thread_pool));
1181    }
1182
1183    #[test]
1184    fn test_next_entry() {
1185        let zero = Hash::default();
1186        let tick = next_entry(&zero, 1, vec![]);
1187        assert_eq!(tick.num_hashes, 1);
1188        assert_ne!(tick.hash, zero);
1189
1190        let tick = next_entry(&zero, 0, vec![]);
1191        assert_eq!(tick.num_hashes, 0);
1192        assert_eq!(tick.hash, zero);
1193
1194        let keypair = Keypair::new();
1195        let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1196        let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1197        assert_eq!(entry0.num_hashes, 1);
1198        assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1199    }
1200
1201    #[test]
1202    #[should_panic]
1203    fn test_next_entry_panic() {
1204        let zero = Hash::default();
1205        let keypair = Keypair::new();
1206        let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1207        next_entry(&zero, 0, vec![tx]);
1208    }
1209
1210    #[test]
1211    fn test_verify_slice1() {
1212        solana_logger::setup();
1213        let thread_pool = thread_pool_for_tests();
1214
1215        let zero = Hash::default();
1216        let one = hash(zero.as_ref());
1217        // base case
1218        assert!(vec![][..].verify(&zero, &thread_pool));
1219        // singleton case 1
1220        assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero, &thread_pool));
1221        // singleton case 2, bad
1222        assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one, &thread_pool));
1223        // inductive step
1224        assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero, &thread_pool));
1225
1226        let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1227        bad_ticks[1].hash = one;
1228        // inductive step, bad
1229        assert!(!bad_ticks.verify(&zero, &thread_pool));
1230    }
1231
1232    #[test]
1233    fn test_verify_slice_with_hashes1() {
1234        solana_logger::setup();
1235        let thread_pool = thread_pool_for_tests();
1236
1237        let zero = Hash::default();
1238        let one = hash(zero.as_ref());
1239        let two = hash(one.as_ref());
1240        // base case
1241        assert!(vec![][..].verify(&one, &thread_pool));
1242        // singleton case 1
1243        assert!(vec![Entry::new_tick(1, &two)][..].verify(&one, &thread_pool));
1244        // singleton case 2, bad
1245        assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two, &thread_pool));
1246
1247        let mut ticks = vec![next_entry(&one, 1, vec![])];
1248        ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1249        // inductive step
1250        assert!(ticks.verify(&one, &thread_pool));
1251
1252        let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1253        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1254        bad_ticks[1].hash = one;
1255        // inductive step, bad
1256        assert!(!bad_ticks.verify(&one, &thread_pool));
1257    }
1258
1259    #[test]
1260    fn test_verify_slice_with_hashes_and_transactions() {
1261        solana_logger::setup();
1262        let thread_pool = thread_pool_for_tests();
1263
1264        let zero = Hash::default();
1265        let one = hash(zero.as_ref());
1266        let two = hash(one.as_ref());
1267        let alice_keypair = Keypair::new();
1268        let bob_keypair = Keypair::new();
1269        let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1270        let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1271        // base case
1272        assert!(vec![][..].verify(&one, &thread_pool));
1273        // singleton case 1
1274        assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one, &thread_pool));
1275        // singleton case 2, bad
1276        assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two, &thread_pool));
1277
1278        let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1279        ticks.push(next_entry(
1280            &ticks.last().unwrap().hash,
1281            1,
1282            vec![tx1.clone()],
1283        ));
1284
1285        // inductive step
1286        assert!(ticks.verify(&one, &thread_pool));
1287
1288        let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1289        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1290        bad_ticks[1].hash = one;
1291        // inductive step, bad
1292        assert!(!bad_ticks.verify(&one, &thread_pool));
1293    }
1294
1295    #[test]
1296    fn test_verify_tick_hash_count() {
1297        let hashes_per_tick = 10;
1298        let tx = VersionedTransaction::default();
1299
1300        let no_hash_tx_entry = Entry {
1301            transactions: vec![tx.clone()],
1302            ..Entry::default()
1303        };
1304        let single_hash_tx_entry = Entry {
1305            transactions: vec![tx.clone()],
1306            num_hashes: 1,
1307            ..Entry::default()
1308        };
1309        let partial_tx_entry = Entry {
1310            num_hashes: hashes_per_tick - 1,
1311            transactions: vec![tx.clone()],
1312            ..Entry::default()
1313        };
1314        let full_tx_entry = Entry {
1315            num_hashes: hashes_per_tick,
1316            transactions: vec![tx.clone()],
1317            ..Entry::default()
1318        };
1319        let max_hash_tx_entry = Entry {
1320            transactions: vec![tx],
1321            num_hashes: u64::MAX,
1322            ..Entry::default()
1323        };
1324
1325        let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1326        let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1327        let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1328        let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1329        let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1330
1331        // empty batch should succeed if hashes_per_tick hasn't been reached
1332        let mut tick_hash_count = 0;
1333        let mut entries = vec![];
1334        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1335        assert_eq!(tick_hash_count, 0);
1336
1337        // empty batch should fail if hashes_per_tick has been reached
1338        tick_hash_count = hashes_per_tick;
1339        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1340        assert_eq!(tick_hash_count, hashes_per_tick);
1341        tick_hash_count = 0;
1342
1343        // validation is disabled when hashes_per_tick == 0
1344        entries = vec![max_hash_tx_entry.clone()];
1345        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1346        assert_eq!(tick_hash_count, 0);
1347
1348        // partial tick should fail
1349        entries = vec![partial_tick_entry.clone()];
1350        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1351        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1352        tick_hash_count = 0;
1353
1354        // full tick entry should succeed
1355        entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1356        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1357        assert_eq!(tick_hash_count, 0);
1358
1359        // oversized tick entry should fail
1360        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1361        assert_eq!(tick_hash_count, hashes_per_tick);
1362        tick_hash_count = 0;
1363
1364        // partial tx entry without tick entry should succeed
1365        entries = vec![partial_tx_entry];
1366        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1367        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1368        tick_hash_count = 0;
1369
1370        // full tx entry with tick entry should succeed
1371        entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1372        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1373        assert_eq!(tick_hash_count, 0);
1374
1375        // full tx entry with oversized tick entry should fail
1376        entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1377        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1378        assert_eq!(tick_hash_count, hashes_per_tick + 1);
1379        tick_hash_count = 0;
1380
1381        // full tx entry without tick entry should fail
1382        entries = vec![full_tx_entry];
1383        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1384        assert_eq!(tick_hash_count, hashes_per_tick);
1385        tick_hash_count = 0;
1386
1387        // tx entry and a tick should succeed
1388        entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1389        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1390        assert_eq!(tick_hash_count, 0);
1391
1392        // many tx entries and a tick should succeed
1393        let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1394            .map(|_| single_hash_tx_entry.clone())
1395            .collect();
1396        entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1397        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1398        assert_eq!(tick_hash_count, 0);
1399
1400        // check overflow saturation should fail
1401        entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1402        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1403        assert_eq!(tick_hash_count, u64::MAX);
1404        tick_hash_count = 0;
1405
1406        // check overflow saturation should fail
1407        entries = vec![max_hash_tx_entry, full_tick_entry];
1408        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1409        assert_eq!(tick_hash_count, u64::MAX);
1410    }
1411
1412    #[test]
1413    fn test_poh_verify_fuzz() {
1414        solana_logger::setup();
1415        for _ in 0..100 {
1416            let mut time = Measure::start("ticks");
1417            let num_ticks = thread_rng().gen_range(1..100);
1418            info!("create {} ticks:", num_ticks);
1419            let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1420            time.stop();
1421
1422            let mut modified = false;
1423            if thread_rng().gen_ratio(1, 2) {
1424                modified = true;
1425                let modify_idx = thread_rng().gen_range(0..num_ticks) as usize;
1426                entries[modify_idx].hash = hash(&[1, 2, 3]);
1427            }
1428
1429            info!("done.. {}", time);
1430            let mut time = Measure::start("poh");
1431            let res = entries.verify(&Hash::default(), &thread_pool_for_tests());
1432            assert_eq!(res, !modified);
1433            time.stop();
1434            info!("{} {}", time, res);
1435        }
1436    }
1437}