clone_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    clone_solana_hash::Hash,
8    clone_solana_measure::measure::Measure,
9    clone_solana_merkle_tree::MerkleTree,
10    clone_solana_metrics::*,
11    clone_solana_packet::Meta,
12    clone_solana_perf::{
13        cuda_runtime::PinnedVec,
14        packet::{Packet, PacketBatch, PacketBatchRecycler, PACKETS_PER_BATCH},
15        perf_libs,
16        recycler::Recycler,
17        sigverify,
18    },
19    clone_solana_rayon_threadlimit::get_max_thread_count,
20    clone_solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
21    clone_solana_transaction::{
22        versioned::VersionedTransaction, Transaction, TransactionVerificationMode,
23    },
24    clone_solana_transaction_error::{TransactionError, TransactionResult as Result},
25    crossbeam_channel::{Receiver, Sender},
26    dlopen2::symbor::{Container, SymBorApi, Symbol},
27    log::*,
28    rand::{thread_rng, Rng},
29    rayon::{prelude::*, ThreadPool},
30    serde::{Deserialize, Serialize},
31    std::{
32        cmp,
33        ffi::OsStr,
34        iter::repeat_with,
35        sync::{Arc, Mutex, Once, OnceLock},
36        thread::{self, JoinHandle},
37        time::Instant,
38    },
39};
40
41pub type EntrySender = Sender<Vec<Entry>>;
42pub type EntryReceiver = Receiver<Vec<Entry>>;
43
44static API: OnceLock<Container<Api>> = OnceLock::new();
45
46pub fn init_poh() {
47    init(OsStr::new("libpoh-simd.so"));
48}
49
50fn init(name: &OsStr) {
51    static INIT_HOOK: Once = Once::new();
52
53    info!("Loading {:?}", name);
54    INIT_HOOK.call_once(|| {
55        let path;
56        let lib_name =
57            if let Some(perf_libs_path) = clone_solana_perf::perf_libs::locate_perf_libs() {
58                clone_solana_perf::perf_libs::append_to_ld_library_path(
59                    perf_libs_path.to_str().unwrap_or("").to_string(),
60                );
61                path = perf_libs_path.join(name);
62                path.as_os_str()
63            } else {
64                name
65            };
66
67        match unsafe { Container::load(lib_name) } {
68            Ok(api) => _ = API.set(api),
69            Err(err) => error!("Unable to load {lib_name:?}: {err}"),
70        }
71    })
72}
73
74pub fn api() -> Option<&'static Container<Api<'static>>> {
75    {
76        static INIT_HOOK: Once = Once::new();
77        INIT_HOOK.call_once(|| {
78            if std::env::var("TEST_PERF_LIBS").is_ok() {
79                init_poh()
80            }
81        });
82    }
83
84    API.get()
85}
86
87#[derive(SymBorApi)]
88pub struct Api<'a> {
89    pub poh_verify_many_simd_avx512skx:
90        Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
91    pub poh_verify_many_simd_avx2:
92        Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
93}
94
95/// 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: `clone_solana_core::banking_stage::BankingStage::process_and_record_transactions()`
114/// * For TVU: `clone_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 `clone_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<Tx: TransactionWithMeta> {
154    Transactions(Vec<Tx>),
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<Tx: TransactionWithMeta> {
289    verification_status: EntryVerificationStatus,
290    entries: Option<Vec<EntryType<Tx>>>,
291    device_verification_data: DeviceSigVerificationData,
292    gpu_verify_duration_us: u64,
293}
294
295impl<Tx: TransactionWithMeta> EntrySigVerificationState<Tx> {
296    pub fn entries(&mut self) -> Option<Vec<EntryType<Tx>>> {
297        self.entries.take()
298    }
299    pub fn finish_verify(&mut self) -> bool {
300        match &mut self.device_verification_data {
301            DeviceSigVerificationData::Gpu(verification_state) => {
302                let (verified, gpu_time_us) =
303                    verification_state.thread_h.take().unwrap().join().unwrap();
304                self.gpu_verify_duration_us = gpu_time_us;
305                self.verification_status = if verified {
306                    EntryVerificationStatus::Success
307                } else {
308                    EntryVerificationStatus::Failure
309                };
310                verified
311            }
312            DeviceSigVerificationData::Cpu() => {
313                self.verification_status == EntryVerificationStatus::Success
314            }
315        }
316    }
317    pub fn status(&self) -> EntryVerificationStatus {
318        self.verification_status
319    }
320    pub fn gpu_verify_duration(&self) -> u64 {
321        self.gpu_verify_duration_us
322    }
323}
324
325#[derive(Default, Clone)]
326pub struct VerifyRecyclers {
327    hash_recycler: Recycler<PinnedVec<Hash>>,
328    tick_count_recycler: Recycler<PinnedVec<u64>>,
329    packet_recycler: PacketBatchRecycler,
330    out_recycler: Recycler<PinnedVec<u8>>,
331    tx_offset_recycler: Recycler<sigverify::TxOffset>,
332}
333
334#[derive(PartialEq, Eq, Clone, Copy, Debug)]
335pub enum EntryVerificationStatus {
336    Failure,
337    Success,
338    Pending,
339}
340
341impl EntryVerificationState {
342    pub fn status(&self) -> EntryVerificationStatus {
343        self.verification_status
344    }
345
346    pub fn poh_duration_us(&self) -> u64 {
347        self.poh_duration_us
348    }
349
350    pub fn finish_verify(&mut self, thread_pool: &ThreadPool) -> bool {
351        match &mut self.device_verification_data {
352            DeviceVerificationData::Gpu(verification_state) => {
353                let gpu_time_us = verification_state.thread_h.take().unwrap().join().unwrap();
354
355                let mut verify_check_time = Measure::start("verify_check");
356                let hashes = verification_state.hashes.take().unwrap();
357                let hashes = Arc::try_unwrap(hashes)
358                    .expect("unwrap Arc")
359                    .into_inner()
360                    .expect("into_inner");
361                let res = thread_pool.install(|| {
362                    hashes
363                        .into_par_iter()
364                        .cloned()
365                        .zip(verification_state.verifications.take().unwrap())
366                        .all(|(hash, (action, expected))| {
367                            let actual = match action {
368                                VerifyAction::Mixin(mixin) => {
369                                    Poh::new(hash, None).record(mixin).unwrap().hash
370                                }
371                                VerifyAction::Tick => Poh::new(hash, None).tick().unwrap().hash,
372                                VerifyAction::None => hash,
373                            };
374                            actual == expected
375                        })
376                });
377                verify_check_time.stop();
378                self.poh_duration_us += gpu_time_us + verify_check_time.as_us();
379
380                self.verification_status = if res {
381                    EntryVerificationStatus::Success
382                } else {
383                    EntryVerificationStatus::Failure
384                };
385                res
386            }
387            DeviceVerificationData::Cpu() => {
388                self.verification_status == EntryVerificationStatus::Success
389            }
390        }
391    }
392}
393
394pub fn verify_transactions<Tx: TransactionWithMeta + Send + Sync>(
395    entries: Vec<Entry>,
396    thread_pool: &ThreadPool,
397    verify: Arc<dyn Fn(VersionedTransaction) -> Result<Tx> + Send + Sync>,
398) -> Result<Vec<EntryType<Tx>>> {
399    thread_pool.install(|| {
400        entries
401            .into_par_iter()
402            .map(|entry| {
403                if entry.transactions.is_empty() {
404                    Ok(EntryType::Tick(entry.hash))
405                } else {
406                    Ok(EntryType::Transactions(
407                        entry
408                            .transactions
409                            .into_par_iter()
410                            .map(verify.as_ref())
411                            .collect::<Result<Vec<_>>>()?,
412                    ))
413                }
414            })
415            .collect()
416    })
417}
418
419pub fn start_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
420    entries: Vec<Entry>,
421    skip_verification: bool,
422    thread_pool: &ThreadPool,
423    verify_recyclers: VerifyRecyclers,
424    verify: Arc<
425        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
426    >,
427) -> Result<EntrySigVerificationState<Tx>> {
428    let api = perf_libs::api();
429
430    // Use the CPU if we have too few transactions for GPU signature verification to be worth it.
431    // We will also use the CPU if no acceleration API is used or if we're skipping
432    // the signature verification as we'd have nothing to do on the GPU in that case.
433    // TODO: make the CPU-to GPU crossover point dynamic, perhaps based on similar future
434    // heuristics to what might be used in sigverify::ed25519_verify when a dynamic crossover
435    // is introduced for that function (see TODO in sigverify::ed25519_verify)
436    let use_cpu = skip_verification
437        || api.is_none()
438        || entries
439            .iter()
440            .try_fold(0, |accum: usize, entry: &Entry| -> Option<usize> {
441                if accum.saturating_add(entry.transactions.len()) < 512 {
442                    Some(accum.saturating_add(entry.transactions.len()))
443                } else {
444                    None
445                }
446            })
447            .is_some();
448
449    if use_cpu {
450        start_verify_transactions_cpu(entries, skip_verification, thread_pool, verify)
451    } else {
452        start_verify_transactions_gpu(entries, verify_recyclers, thread_pool, verify)
453    }
454}
455
456fn start_verify_transactions_cpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
457    entries: Vec<Entry>,
458    skip_verification: bool,
459    thread_pool: &ThreadPool,
460    verify: Arc<
461        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
462    >,
463) -> Result<EntrySigVerificationState<Tx>> {
464    let verify_func = {
465        let mode = if skip_verification {
466            TransactionVerificationMode::HashOnly
467        } else {
468            TransactionVerificationMode::FullVerification
469        };
470
471        move |versioned_tx| verify(versioned_tx, mode)
472    };
473
474    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
475
476    Ok(EntrySigVerificationState {
477        verification_status: EntryVerificationStatus::Success,
478        entries: Some(entries),
479        device_verification_data: DeviceSigVerificationData::Cpu(),
480        gpu_verify_duration_us: 0,
481    })
482}
483
484fn start_verify_transactions_gpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
485    entries: Vec<Entry>,
486    verify_recyclers: VerifyRecyclers,
487    thread_pool: &ThreadPool,
488    verify: Arc<
489        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
490    >,
491) -> Result<EntrySigVerificationState<Tx>> {
492    let verify_func = {
493        move |versioned_tx: VersionedTransaction| -> Result<Tx> {
494            verify(
495                versioned_tx,
496                TransactionVerificationMode::HashAndVerifyPrecompiles,
497            )
498        }
499    };
500
501    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
502
503    let transactions = entries
504        .iter()
505        .filter_map(|entry_type| match entry_type {
506            EntryType::Tick(_) => None,
507            EntryType::Transactions(transactions) => Some(transactions),
508        })
509        .flatten()
510        .collect::<Vec<_>>();
511
512    if transactions.is_empty() {
513        return Ok(EntrySigVerificationState {
514            verification_status: EntryVerificationStatus::Success,
515            entries: Some(entries),
516            device_verification_data: DeviceSigVerificationData::Cpu(),
517            gpu_verify_duration_us: 0,
518        });
519    }
520
521    let packet_batches = thread_pool.install(|| {
522        transactions
523            .par_chunks(PACKETS_PER_BATCH)
524            .map(|transaction_chunk| {
525                let num_transactions = transaction_chunk.len();
526                let mut packet_batch = PacketBatch::new_with_recycler(
527                    &verify_recyclers.packet_recycler,
528                    num_transactions,
529                    "entry-sig-verify",
530                );
531                // We use set_len here instead of resize(num_txs, Packet::default()), to save
532                // memory bandwidth and avoid writing a large amount of data that will be overwritten
533                // soon afterwards. As well, Packet::default() actually leaves the packet data
534                // uninitialized, so the initialization would simply write junk into
535                // the vector anyway.
536                unsafe {
537                    packet_batch.set_len(num_transactions);
538                }
539                let transaction_iter = transaction_chunk
540                    .iter()
541                    .map(|tx| tx.to_versioned_transaction());
542
543                let res = packet_batch
544                    .iter_mut()
545                    .zip(transaction_iter)
546                    .all(|(packet, tx)| {
547                        *packet.meta_mut() = Meta::default();
548                        Packet::populate_packet(packet, None, &tx).is_ok()
549                    });
550                if res {
551                    Ok(packet_batch)
552                } else {
553                    Err(TransactionError::SanitizeFailure)
554                }
555            })
556            .collect::<Result<Vec<_>>>()
557    });
558    let mut packet_batches = packet_batches?;
559
560    let tx_offset_recycler = verify_recyclers.tx_offset_recycler;
561    let out_recycler = verify_recyclers.out_recycler;
562    let num_packets = transactions.len();
563    let gpu_verify_thread = thread::Builder::new()
564        .name("solGpuSigVerify".into())
565        .spawn(move || {
566            let mut verify_time = Measure::start("sigverify");
567            sigverify::ed25519_verify(
568                &mut packet_batches,
569                &tx_offset_recycler,
570                &out_recycler,
571                false,
572                num_packets,
573            );
574            let verified = packet_batches
575                .iter()
576                .all(|batch| batch.iter().all(|p| !p.meta().discard()));
577            verify_time.stop();
578            (verified, verify_time.as_us())
579        })
580        .unwrap();
581
582    Ok(EntrySigVerificationState {
583        verification_status: EntryVerificationStatus::Pending,
584        entries: Some(entries),
585        device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData {
586            thread_h: Some(gpu_verify_thread),
587        }),
588        gpu_verify_duration_us: 0,
589    })
590}
591
592fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
593    let actual = if !ref_entry.transactions.is_empty() {
594        let tx_hash = hash_transactions(&ref_entry.transactions);
595        let mut poh = Poh::new(computed_hash, None);
596        poh.record(tx_hash).unwrap().hash
597    } else if ref_entry.num_hashes > 0 {
598        let mut poh = Poh::new(computed_hash, None);
599        poh.tick().unwrap().hash
600    } else {
601        computed_hash
602    };
603    actual == ref_entry.hash
604}
605
606// an EntrySlice is a slice of Entries
607pub trait EntrySlice {
608    /// Verifies the hashes and counts of a slice of transactions are all consistent.
609    fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState;
610    fn verify_cpu_generic(
611        &self,
612        start_hash: &Hash,
613        thread_pool: &ThreadPool,
614    ) -> EntryVerificationState;
615    fn verify_cpu_x86_simd(
616        &self,
617        start_hash: &Hash,
618        simd_len: usize,
619        thread_pool: &ThreadPool,
620    ) -> EntryVerificationState;
621    fn start_verify(
622        &self,
623        start_hash: &Hash,
624        thread_pool: &ThreadPool,
625        recyclers: VerifyRecyclers,
626    ) -> EntryVerificationState;
627    fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool;
628    /// Checks that each entry tick has the correct number of hashes. Entry slices do not
629    /// necessarily end in a tick, so `tick_hash_count` is used to carry over the hash count
630    /// for the next entry slice.
631    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
632    /// Counts tick entries
633    fn tick_count(&self) -> u64;
634}
635
636impl EntrySlice for [Entry] {
637    fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> bool {
638        self.start_verify(start_hash, thread_pool, VerifyRecyclers::default())
639            .finish_verify(thread_pool)
640    }
641
642    fn verify_cpu_generic(
643        &self,
644        start_hash: &Hash,
645        thread_pool: &ThreadPool,
646    ) -> EntryVerificationState {
647        let now = Instant::now();
648        let genesis = [Entry {
649            num_hashes: 0,
650            hash: *start_hash,
651            transactions: vec![],
652        }];
653        let entry_pairs = genesis.par_iter().chain(self).zip(self);
654        let res = thread_pool.install(|| {
655            entry_pairs.all(|(x0, x1)| {
656                let r = x1.verify(&x0.hash);
657                if !r {
658                    warn!(
659                        "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
660                        x0.hash,
661                        x1.hash,
662                        x1.transactions.len()
663                    );
664                }
665                r
666            })
667        });
668        let poh_duration_us = now.elapsed().as_micros() as u64;
669        EntryVerificationState {
670            verification_status: if res {
671                EntryVerificationStatus::Success
672            } else {
673                EntryVerificationStatus::Failure
674            },
675            poh_duration_us,
676            device_verification_data: DeviceVerificationData::Cpu(),
677        }
678    }
679
680    fn verify_cpu_x86_simd(
681        &self,
682        start_hash: &Hash,
683        simd_len: usize,
684        thread_pool: &ThreadPool,
685    ) -> EntryVerificationState {
686        use clone_solana_hash::HASH_BYTES;
687        let now = Instant::now();
688        let genesis = [Entry {
689            num_hashes: 0,
690            hash: *start_hash,
691            transactions: vec![],
692        }];
693
694        let aligned_len = self.len().div_ceil(simd_len) * simd_len;
695        let mut hashes_bytes = vec![0u8; HASH_BYTES * aligned_len];
696        genesis
697            .iter()
698            .chain(self)
699            .enumerate()
700            .for_each(|(i, entry)| {
701                if i < self.len() {
702                    let start = i * HASH_BYTES;
703                    let end = start + HASH_BYTES;
704                    hashes_bytes[start..end].copy_from_slice(&entry.hash.to_bytes());
705                }
706            });
707        let mut hashes_chunked: Vec<_> = hashes_bytes.chunks_mut(simd_len * HASH_BYTES).collect();
708
709        let mut num_hashes: Vec<u64> = self
710            .iter()
711            .map(|entry| entry.num_hashes.saturating_sub(1))
712            .collect();
713        num_hashes.resize(aligned_len, 0);
714        let num_hashes: Vec<_> = num_hashes.chunks(simd_len).collect();
715
716        let res = thread_pool.install(|| {
717            hashes_chunked
718                .par_iter_mut()
719                .zip(num_hashes)
720                .enumerate()
721                .all(|(i, (chunk, num_hashes))| {
722                    match simd_len {
723                        8 => unsafe {
724                            (api().unwrap().poh_verify_many_simd_avx2)(
725                                chunk.as_mut_ptr(),
726                                num_hashes.as_ptr(),
727                            );
728                        },
729                        16 => unsafe {
730                            (api().unwrap().poh_verify_many_simd_avx512skx)(
731                                chunk.as_mut_ptr(),
732                                num_hashes.as_ptr(),
733                            );
734                        },
735                        _ => {
736                            panic!("unsupported simd len: {simd_len}");
737                        }
738                    }
739                    let entry_start = i * simd_len;
740                    // The last chunk may produce indexes larger than what we have in the reference entries
741                    // because it is aligned to simd_len.
742                    let entry_end = std::cmp::min(entry_start + simd_len, self.len());
743                    self[entry_start..entry_end]
744                        .iter()
745                        .enumerate()
746                        .all(|(j, ref_entry)| {
747                            let start = j * HASH_BYTES;
748                            let end = start + HASH_BYTES;
749                            let hash = <[u8; HASH_BYTES]>::try_from(&chunk[start..end])
750                                .map(Hash::new_from_array)
751                                .unwrap();
752                            compare_hashes(hash, ref_entry)
753                        })
754                })
755        });
756        let poh_duration_us = now.elapsed().as_micros() as u64;
757        EntryVerificationState {
758            verification_status: if res {
759                EntryVerificationStatus::Success
760            } else {
761                EntryVerificationStatus::Failure
762            },
763            poh_duration_us,
764            device_verification_data: DeviceVerificationData::Cpu(),
765        }
766    }
767
768    fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
769        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
770        let (has_avx2, has_avx512) = (
771            is_x86_feature_detected!("avx2"),
772            is_x86_feature_detected!("avx512f"),
773        );
774        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
775        let (has_avx2, has_avx512) = (false, false);
776
777        if api().is_some() {
778            if has_avx512 && self.len() >= 128 {
779                self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
780            } else if has_avx2 && self.len() >= 48 {
781                self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
782            } else {
783                self.verify_cpu_generic(start_hash, thread_pool)
784            }
785        } else {
786            self.verify_cpu_generic(start_hash, thread_pool)
787        }
788    }
789
790    fn start_verify(
791        &self,
792        start_hash: &Hash,
793        thread_pool: &ThreadPool,
794        recyclers: VerifyRecyclers,
795    ) -> EntryVerificationState {
796        let start = Instant::now();
797        let Some(api) = perf_libs::api() else {
798            return self.verify_cpu(start_hash, thread_pool);
799        };
800        inc_new_counter_info!("entry_verify-num_entries", self.len());
801
802        let genesis = [Entry {
803            num_hashes: 0,
804            hash: *start_hash,
805            transactions: vec![],
806        }];
807
808        let hashes: Vec<Hash> = genesis
809            .iter()
810            .chain(self)
811            .map(|entry| entry.hash)
812            .take(self.len())
813            .collect();
814
815        let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
816        hashes_pinned.set_pinnable();
817        hashes_pinned.resize(hashes.len(), Hash::default());
818        hashes_pinned.copy_from_slice(&hashes);
819
820        let mut num_hashes_vec = recyclers
821            .tick_count_recycler
822            .allocate("poh_verify_num_hashes");
823        num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
824        for entry in self {
825            num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
826        }
827
828        let length = self.len();
829        let hashes = Arc::new(Mutex::new(hashes_pinned));
830        let hashes_clone = hashes.clone();
831
832        let gpu_verify_thread = thread::Builder::new()
833            .name("solGpuPohVerify".into())
834            .spawn(move || {
835                let mut hashes = hashes_clone.lock().unwrap();
836                let gpu_wait = Instant::now();
837                let res;
838                unsafe {
839                    res = (api.poh_verify_many)(
840                        hashes.as_mut_ptr() as *mut u8,
841                        num_hashes_vec.as_ptr(),
842                        length,
843                        1,
844                    );
845                }
846                assert!(res == 0, "GPU PoH verify many failed");
847                inc_new_counter_info!(
848                    "entry_verify-gpu_thread",
849                    gpu_wait.elapsed().as_micros() as usize
850                );
851                gpu_wait.elapsed().as_micros() as u64
852            })
853            .unwrap();
854
855        let verifications = thread_pool.install(|| {
856            self.into_par_iter()
857                .map(|entry| {
858                    let answer = entry.hash;
859                    let action = if entry.transactions.is_empty() {
860                        if entry.num_hashes == 0 {
861                            VerifyAction::None
862                        } else {
863                            VerifyAction::Tick
864                        }
865                    } else {
866                        VerifyAction::Mixin(hash_transactions(&entry.transactions))
867                    };
868                    (action, answer)
869                })
870                .collect()
871        });
872        let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
873            thread_h: Some(gpu_verify_thread),
874            verifications: Some(verifications),
875            hashes: Some(hashes),
876        });
877        EntryVerificationState {
878            verification_status: EntryVerificationStatus::Pending,
879            poh_duration_us: start.elapsed().as_micros() as u64,
880            device_verification_data,
881        }
882    }
883
884    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
885        // When hashes_per_tick is 0, hashing is disabled.
886        if hashes_per_tick == 0 {
887            return true;
888        }
889
890        for entry in self {
891            *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
892            if entry.is_tick() {
893                if *tick_hash_count != hashes_per_tick {
894                    warn!(
895                        "invalid tick hash count!: entry: {:#?}, tick_hash_count: {}, hashes_per_tick: {}",
896                        entry,
897                        tick_hash_count,
898                        hashes_per_tick
899                    );
900                    return false;
901                }
902                *tick_hash_count = 0;
903            }
904        }
905        *tick_hash_count < hashes_per_tick
906    }
907
908    fn tick_count(&self) -> u64 {
909        self.iter().filter(|e| e.is_tick()).count() as u64
910    }
911}
912
913pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
914    let entry = Entry::new(start, num_hashes, transactions);
915    *start = entry.hash;
916    entry
917}
918
919pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
920    repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
921        .take(num_ticks as usize)
922        .collect()
923}
924
925pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
926    repeat_with(|| {
927        let hashes_per_tick = thread_rng().gen_range(1..max_hashes_per_tick);
928        next_entry_mut(&mut hash, hashes_per_tick, vec![])
929    })
930    .take(num_ticks as usize)
931    .collect()
932}
933
934/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
935pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
936    let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
937    next_versioned_entry(prev_hash, num_hashes, transactions)
938}
939
940/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
941pub fn next_versioned_entry(
942    prev_hash: &Hash,
943    num_hashes: u64,
944    transactions: Vec<VersionedTransaction>,
945) -> Entry {
946    assert!(num_hashes > 0 || transactions.is_empty());
947    Entry {
948        num_hashes,
949        hash: next_hash(prev_hash, num_hashes, &transactions),
950        transactions,
951    }
952}
953
954pub fn thread_pool_for_tests() -> ThreadPool {
955    // Allocate fewer threads for unit tests
956    // Unit tests typically aren't creating massive blocks to verify, and
957    // multiple tests could be running in parallel so any further parallelism
958    // will do more harm than good
959    rayon::ThreadPoolBuilder::new()
960        .num_threads(4)
961        .thread_name(|i| format!("solEntryTest{i:02}"))
962        .build()
963        .expect("new rayon threadpool")
964}
965
966pub fn thread_pool_for_benches() -> ThreadPool {
967    rayon::ThreadPoolBuilder::new()
968        .num_threads(get_max_thread_count())
969        .thread_name(|i| format!("solEntryBnch{i:02}"))
970        .build()
971        .expect("new rayon threadpool")
972}
973
974#[cfg(test)]
975mod tests {
976    use {
977        super::*,
978        clone_agave_reserved_account_keys::ReservedAccountKeys,
979        clone_solana_hash::Hash,
980        clone_solana_keypair::Keypair,
981        clone_solana_message::SimpleAddressLoader,
982        clone_solana_perf::test_tx::{test_invalid_tx, test_tx},
983        clone_solana_pubkey::Pubkey,
984        clone_solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
985        clone_solana_sha256_hasher::hash,
986        clone_solana_signer::Signer,
987        clone_solana_system_transaction as system_transaction,
988        clone_solana_transaction::{
989            sanitized::{MessageHash, SanitizedTransaction},
990            versioned::VersionedTransaction,
991        },
992        clone_solana_transaction_error::TransactionResult as Result,
993    };
994
995    #[test]
996    fn test_entry_verify() {
997        let zero = Hash::default();
998        let one = hash(zero.as_ref());
999        assert!(Entry::new_tick(0, &zero).verify(&zero)); // 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<Tx: TransactionWithMeta + Send + Sync + 'static>(
1006        entries: Vec<Entry>,
1007        skip_verification: bool,
1008        verify_recyclers: VerifyRecyclers,
1009        thread_pool: &ThreadPool,
1010        verify: Arc<
1011            dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
1012        >,
1013    ) -> bool {
1014        let verify_func = {
1015            let verify = verify.clone();
1016            let verification_mode = if skip_verification {
1017                TransactionVerificationMode::HashOnly
1018            } else {
1019                TransactionVerificationMode::FullVerification
1020            };
1021            move |versioned_tx: VersionedTransaction| -> Result<Tx> {
1022                verify(versioned_tx, verification_mode)
1023            }
1024        };
1025
1026        let cpu_verify_result =
1027            verify_transactions(entries.clone(), thread_pool, Arc::new(verify_func));
1028        let mut gpu_verify_result: EntrySigVerificationState<Tx> = {
1029            let verify_result = start_verify_transactions(
1030                entries,
1031                skip_verification,
1032                thread_pool,
1033                verify_recyclers,
1034                verify,
1035            );
1036            match verify_result {
1037                Ok(res) => res,
1038                _ => EntrySigVerificationState {
1039                    verification_status: EntryVerificationStatus::Failure,
1040                    entries: None,
1041                    device_verification_data: DeviceSigVerificationData::Cpu(),
1042                    gpu_verify_duration_us: 0,
1043                },
1044            }
1045        };
1046
1047        match cpu_verify_result {
1048            Ok(_) => {
1049                assert!(gpu_verify_result.verification_status != EntryVerificationStatus::Failure);
1050                assert!(gpu_verify_result.finish_verify());
1051                true
1052            }
1053            _ => {
1054                assert!(
1055                    gpu_verify_result.verification_status == EntryVerificationStatus::Failure
1056                        || !gpu_verify_result.finish_verify()
1057                );
1058                false
1059            }
1060        }
1061    }
1062
1063    #[test]
1064    fn test_entry_gpu_verify() {
1065        let thread_pool = thread_pool_for_tests();
1066
1067        let verify_transaction = {
1068            move |versioned_tx: VersionedTransaction,
1069                  verification_mode: TransactionVerificationMode|
1070                  -> Result<RuntimeTransaction<SanitizedTransaction>> {
1071                let sanitized_tx = {
1072                    let message_hash =
1073                        if verification_mode == TransactionVerificationMode::FullVerification {
1074                            versioned_tx.verify_and_hash_message()?
1075                        } else {
1076                            versioned_tx.message.hash()
1077                        };
1078
1079                    RuntimeTransaction::try_create(
1080                        versioned_tx,
1081                        MessageHash::Precomputed(message_hash),
1082                        None,
1083                        SimpleAddressLoader::Disabled,
1084                        &ReservedAccountKeys::empty_key_set(),
1085                    )
1086                }?;
1087
1088                Ok(sanitized_tx)
1089            }
1090        };
1091
1092        let recycler = VerifyRecyclers::default();
1093
1094        // Make sure we test with a number of transactions that's not a multiple of PACKETS_PER_BATCH
1095        let entries_invalid = (0..1025)
1096            .map(|_| {
1097                let transaction = test_invalid_tx();
1098                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1099            })
1100            .collect::<Vec<_>>();
1101
1102        let entries_valid = (0..1025)
1103            .map(|_| {
1104                let transaction = test_tx();
1105                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1106            })
1107            .collect::<Vec<_>>();
1108
1109        assert!(!test_verify_transactions(
1110            entries_invalid,
1111            false,
1112            recycler.clone(),
1113            &thread_pool,
1114            Arc::new(verify_transaction)
1115        ));
1116        assert!(test_verify_transactions(
1117            entries_valid,
1118            false,
1119            recycler,
1120            &thread_pool,
1121            Arc::new(verify_transaction)
1122        ));
1123    }
1124
1125    #[test]
1126    fn test_transaction_reorder_attack() {
1127        let zero = Hash::default();
1128
1129        // First, verify entries
1130        let keypair = Keypair::new();
1131        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1132        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1133        let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
1134        assert!(e0.verify(&zero));
1135
1136        // Next, swap two transactions and ensure verification fails.
1137        e0.transactions[0] = tx1.into(); // <-- attack
1138        e0.transactions[1] = tx0.into();
1139        assert!(!e0.verify(&zero));
1140    }
1141
1142    #[test]
1143    fn test_transaction_signing() {
1144        let thread_pool = thread_pool_for_tests();
1145
1146        use clone_solana_signature::Signature;
1147        let zero = Hash::default();
1148
1149        let keypair = Keypair::new();
1150        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1151        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1152
1153        // Verify entry with 2 transactions
1154        let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
1155        assert!(e0.verify(&zero, &thread_pool));
1156
1157        // Clear signature of the first transaction, see that it does not verify
1158        let orig_sig = e0[0].transactions[0].signatures[0];
1159        e0[0].transactions[0].signatures[0] = Signature::default();
1160        assert!(!e0.verify(&zero, &thread_pool));
1161
1162        // restore original signature
1163        e0[0].transactions[0].signatures[0] = orig_sig;
1164        assert!(e0.verify(&zero, &thread_pool));
1165
1166        // Resize signatures and see verification fails.
1167        let len = e0[0].transactions[0].signatures.len();
1168        e0[0].transactions[0]
1169            .signatures
1170            .resize(len - 1, Signature::default());
1171        assert!(!e0.verify(&zero, &thread_pool));
1172
1173        // Pass an entry with no transactions
1174        let e0 = [Entry::new(&zero, 0, vec![])];
1175        assert!(e0.verify(&zero, &thread_pool));
1176    }
1177
1178    #[test]
1179    fn test_next_entry() {
1180        let zero = Hash::default();
1181        let tick = next_entry(&zero, 1, vec![]);
1182        assert_eq!(tick.num_hashes, 1);
1183        assert_ne!(tick.hash, zero);
1184
1185        let tick = next_entry(&zero, 0, vec![]);
1186        assert_eq!(tick.num_hashes, 0);
1187        assert_eq!(tick.hash, zero);
1188
1189        let keypair = Keypair::new();
1190        let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1191        let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1192        assert_eq!(entry0.num_hashes, 1);
1193        assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1194    }
1195
1196    #[test]
1197    #[should_panic]
1198    fn test_next_entry_panic() {
1199        let zero = Hash::default();
1200        let keypair = Keypair::new();
1201        let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1202        next_entry(&zero, 0, vec![tx]);
1203    }
1204
1205    #[test]
1206    fn test_verify_slice1() {
1207        clone_solana_logger::setup();
1208        let thread_pool = thread_pool_for_tests();
1209
1210        let zero = Hash::default();
1211        let one = hash(zero.as_ref());
1212        // base case
1213        assert!(vec![][..].verify(&zero, &thread_pool));
1214        // singleton case 1
1215        assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero, &thread_pool));
1216        // singleton case 2, bad
1217        assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one, &thread_pool));
1218        // inductive step
1219        assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero, &thread_pool));
1220
1221        let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1222        bad_ticks[1].hash = one;
1223        // inductive step, bad
1224        assert!(!bad_ticks.verify(&zero, &thread_pool));
1225    }
1226
1227    #[test]
1228    fn test_verify_slice_with_hashes1() {
1229        clone_solana_logger::setup();
1230        let thread_pool = thread_pool_for_tests();
1231
1232        let zero = Hash::default();
1233        let one = hash(zero.as_ref());
1234        let two = hash(one.as_ref());
1235        // base case
1236        assert!(vec![][..].verify(&one, &thread_pool));
1237        // singleton case 1
1238        assert!(vec![Entry::new_tick(1, &two)][..].verify(&one, &thread_pool));
1239        // singleton case 2, bad
1240        assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two, &thread_pool));
1241
1242        let mut ticks = vec![next_entry(&one, 1, vec![])];
1243        ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1244        // inductive step
1245        assert!(ticks.verify(&one, &thread_pool));
1246
1247        let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1248        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1249        bad_ticks[1].hash = one;
1250        // inductive step, bad
1251        assert!(!bad_ticks.verify(&one, &thread_pool));
1252    }
1253
1254    #[test]
1255    fn test_verify_slice_with_hashes_and_transactions() {
1256        clone_solana_logger::setup();
1257        let thread_pool = thread_pool_for_tests();
1258
1259        let zero = Hash::default();
1260        let one = hash(zero.as_ref());
1261        let two = hash(one.as_ref());
1262        let alice_keypair = Keypair::new();
1263        let bob_keypair = Keypair::new();
1264        let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1265        let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1266        // base case
1267        assert!(vec![][..].verify(&one, &thread_pool));
1268        // singleton case 1
1269        assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one, &thread_pool));
1270        // singleton case 2, bad
1271        assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two, &thread_pool));
1272
1273        let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1274        ticks.push(next_entry(
1275            &ticks.last().unwrap().hash,
1276            1,
1277            vec![tx1.clone()],
1278        ));
1279
1280        // inductive step
1281        assert!(ticks.verify(&one, &thread_pool));
1282
1283        let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1284        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1285        bad_ticks[1].hash = one;
1286        // inductive step, bad
1287        assert!(!bad_ticks.verify(&one, &thread_pool));
1288    }
1289
1290    #[test]
1291    fn test_verify_tick_hash_count() {
1292        let hashes_per_tick = 10;
1293        let tx = VersionedTransaction::default();
1294
1295        let no_hash_tx_entry = Entry {
1296            transactions: vec![tx.clone()],
1297            ..Entry::default()
1298        };
1299        let single_hash_tx_entry = Entry {
1300            transactions: vec![tx.clone()],
1301            num_hashes: 1,
1302            ..Entry::default()
1303        };
1304        let partial_tx_entry = Entry {
1305            num_hashes: hashes_per_tick - 1,
1306            transactions: vec![tx.clone()],
1307            ..Entry::default()
1308        };
1309        let full_tx_entry = Entry {
1310            num_hashes: hashes_per_tick,
1311            transactions: vec![tx.clone()],
1312            ..Entry::default()
1313        };
1314        let max_hash_tx_entry = Entry {
1315            transactions: vec![tx],
1316            num_hashes: u64::MAX,
1317            ..Entry::default()
1318        };
1319
1320        let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1321        let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1322        let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1323        let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1324        let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1325
1326        // empty batch should succeed if hashes_per_tick hasn't been reached
1327        let mut tick_hash_count = 0;
1328        let mut entries = vec![];
1329        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1330        assert_eq!(tick_hash_count, 0);
1331
1332        // empty batch should fail if hashes_per_tick has been reached
1333        tick_hash_count = hashes_per_tick;
1334        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1335        assert_eq!(tick_hash_count, hashes_per_tick);
1336        tick_hash_count = 0;
1337
1338        // validation is disabled when hashes_per_tick == 0
1339        entries = vec![max_hash_tx_entry.clone()];
1340        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1341        assert_eq!(tick_hash_count, 0);
1342
1343        // partial tick should fail
1344        entries = vec![partial_tick_entry.clone()];
1345        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1346        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1347        tick_hash_count = 0;
1348
1349        // full tick entry should succeed
1350        entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1351        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1352        assert_eq!(tick_hash_count, 0);
1353
1354        // oversized tick entry should fail
1355        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1356        assert_eq!(tick_hash_count, hashes_per_tick);
1357        tick_hash_count = 0;
1358
1359        // partial tx entry without tick entry should succeed
1360        entries = vec![partial_tx_entry];
1361        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1362        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1363        tick_hash_count = 0;
1364
1365        // full tx entry with tick entry should succeed
1366        entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1367        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1368        assert_eq!(tick_hash_count, 0);
1369
1370        // full tx entry with oversized tick entry should fail
1371        entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1372        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1373        assert_eq!(tick_hash_count, hashes_per_tick + 1);
1374        tick_hash_count = 0;
1375
1376        // full tx entry without tick entry should fail
1377        entries = vec![full_tx_entry];
1378        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1379        assert_eq!(tick_hash_count, hashes_per_tick);
1380        tick_hash_count = 0;
1381
1382        // tx entry and a tick should succeed
1383        entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1384        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1385        assert_eq!(tick_hash_count, 0);
1386
1387        // many tx entries and a tick should succeed
1388        let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1389            .map(|_| single_hash_tx_entry.clone())
1390            .collect();
1391        entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1392        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1393        assert_eq!(tick_hash_count, 0);
1394
1395        // check overflow saturation should fail
1396        entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1397        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1398        assert_eq!(tick_hash_count, u64::MAX);
1399        tick_hash_count = 0;
1400
1401        // check overflow saturation should fail
1402        entries = vec![max_hash_tx_entry, full_tick_entry];
1403        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1404        assert_eq!(tick_hash_count, u64::MAX);
1405    }
1406
1407    #[test]
1408    fn test_poh_verify_fuzz() {
1409        clone_solana_logger::setup();
1410        for _ in 0..100 {
1411            let mut time = Measure::start("ticks");
1412            let num_ticks = thread_rng().gen_range(1..100);
1413            info!("create {} ticks:", num_ticks);
1414            let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1415            time.stop();
1416
1417            let mut modified = false;
1418            if thread_rng().gen_ratio(1, 2) {
1419                modified = true;
1420                let modify_idx = thread_rng().gen_range(0..num_ticks) as usize;
1421                entries[modify_idx].hash = hash(&[1, 2, 3]);
1422            }
1423
1424            info!("done.. {}", time);
1425            let mut time = Measure::start("poh");
1426            let res = entries.verify(&Hash::default(), &thread_pool_for_tests());
1427            assert_eq!(res, !modified);
1428            time.stop();
1429            info!("{} {}", time, res);
1430        }
1431    }
1432
1433    #[test]
1434    fn test_hash_transactions() {
1435        let mut transactions: Vec<_> = [test_tx(), test_tx(), test_tx()]
1436            .into_iter()
1437            .map(VersionedTransaction::from)
1438            .collect();
1439
1440        // Test different permutations of the transactions have different final hashes.
1441        // i.e. that **order** of transactions is included in the hash.
1442        let hash1 = hash_transactions(&transactions);
1443        transactions.swap(0, 1);
1444        let hash2 = hash_transactions(&transactions);
1445        assert_ne!(hash1, hash2);
1446    }
1447}