Skip to main content

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_hash::Hash,
14    solana_measure::measure::Measure,
15    solana_merkle_tree::MerkleTree,
16    solana_metrics::*,
17    solana_packet::Meta,
18    solana_perf::{
19        cuda_runtime::PinnedVec,
20        packet::{Packet, PacketBatch, PacketBatchRecycler, PinnedPacketBatch, PACKETS_PER_BATCH},
21        perf_libs,
22        recycler::Recycler,
23        sigverify,
24    },
25    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
26    solana_transaction::{
27        versioned::VersionedTransaction, Transaction, TransactionVerificationMode,
28    },
29    solana_transaction_error::{TransactionError, TransactionResult as Result},
30    std::{
31        cmp,
32        ffi::OsStr,
33        iter::repeat_with,
34        sync::{Arc, Mutex, Once, OnceLock},
35        thread::{self, JoinHandle},
36        time::Instant,
37    },
38    wincode::{
39        containers::{Elem, Pod, Vec as WincodeVec},
40        len::BincodeLen,
41        SchemaRead, SchemaWrite,
42    },
43};
44
45pub type EntrySender = Sender<Vec<Entry>>;
46pub type EntryReceiver = Receiver<Vec<Entry>>;
47
48static API: OnceLock<Container<Api>> = OnceLock::new();
49
50pub fn init_poh() {
51    init(OsStr::new("libpoh-simd.so"));
52}
53
54fn init(name: &OsStr) {
55    static INIT_HOOK: Once = Once::new();
56
57    info!("Loading {name:?}");
58    INIT_HOOK.call_once(|| {
59        let path;
60        let lib_name = if let Some(perf_libs_path) = solana_perf::perf_libs::locate_perf_libs() {
61            solana_perf::perf_libs::append_to_ld_library_path(
62                perf_libs_path.to_str().unwrap_or("").to_string(),
63            );
64            path = perf_libs_path.join(name);
65            path.as_os_str()
66        } else {
67            name
68        };
69
70        match unsafe { Container::load(lib_name) } {
71            Ok(api) => _ = API.set(api),
72            Err(err) => error!("Unable to load {lib_name:?}: {err}"),
73        }
74    })
75}
76
77pub fn api() -> Option<&'static Container<Api<'static>>> {
78    {
79        static INIT_HOOK: Once = Once::new();
80        INIT_HOOK.call_once(|| {
81            if std::env::var("TEST_PERF_LIBS").is_ok() {
82                init_poh()
83            }
84        });
85    }
86
87    API.get()
88}
89
90#[derive(SymBorApi)]
91pub struct Api<'a> {
92    pub poh_verify_many_simd_avx512skx:
93        Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
94    pub poh_verify_many_simd_avx2:
95        Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
96}
97
98const MAX_DATA_SHREDS_PER_SLOT: usize = 32_768;
99pub const MAX_DATA_SHREDS_SIZE: usize = MAX_DATA_SHREDS_PER_SLOT * solana_packet::PACKET_DATA_SIZE;
100pub type MaxDataShredsLen = BincodeLen<MAX_DATA_SHREDS_SIZE>;
101
102/// Each Entry contains three pieces of data. The `num_hashes` field is the number
103/// of hashes performed since the previous entry.  The `hash` field is the result
104/// of hashing `hash` from the previous entry `num_hashes` times.  The `transactions`
105/// field points to Transactions that took place shortly before `hash` was generated.
106///
107/// If you multiply `num_hashes` by the amount of time it takes to generate a new hash, you
108/// get a duration estimate since the last `Entry`. Since processing power increases
109/// over time, one should expect the duration `num_hashes` represents to decrease proportionally.
110/// An upper bound on Duration can be estimated by assuming each hash was generated by the
111/// world's fastest processor at the time the entry was recorded. Or said another way, it
112/// is physically not possible for a shorter duration to have occurred if one assumes the
113/// hash was computed by the world's fastest processor at that time. The hash chain is both
114/// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of
115/// Work consensus!)
116///
117/// The solana core protocol currently requires an `Entry` to contain `transactions` that are
118/// executable in parallel. Implemented in:
119///
120/// * For TPU: `solana_core::banking_stage::BankingStage::process_and_record_transactions()`
121/// * For TVU: `solana_core::replay_stage::ReplayStage::replay_blockstore_into_bank()`
122///
123/// Until SIMD83 is activated:
124/// All transactions in the `transactions` field have to follow the read/write locking restrictions
125/// with regard to the accounts they reference. A single account can be either written by a single
126/// transaction, or read by one or more transactions, but not both.
127/// This enforcement is done via a call to `solana_runtime::accounts::Accounts::lock_accounts()`
128/// with the `txs` argument holding all the `transactions` in the `Entry`.
129#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone, SchemaWrite, SchemaRead)]
130pub struct Entry {
131    /// The number of hashes since the previous Entry ID.
132    pub num_hashes: u64,
133
134    /// The SHA-256 hash `num_hashes` after the previous Entry ID.
135    #[wincode(with = "Pod<Hash>")]
136    pub hash: Hash,
137
138    /// An unordered list of transactions that were observed before the Entry ID was
139    /// generated. They may have been observed before a previous Entry ID but were
140    /// pushed back into this list to ensure deterministic interpretation of the ledger.
141    #[wincode(with = "WincodeVec<Elem<crate::wincode::VersionedTransaction>, MaxDataShredsLen>")]
142    pub transactions: Vec<VersionedTransaction>,
143}
144
145pub struct EntrySummary {
146    pub num_hashes: u64,
147    pub hash: Hash,
148    pub num_transactions: u64,
149}
150
151impl From<&Entry> for EntrySummary {
152    fn from(entry: &Entry) -> Self {
153        Self {
154            num_hashes: entry.num_hashes,
155            hash: entry.hash,
156            num_transactions: entry.transactions.len() as u64,
157        }
158    }
159}
160
161/// Typed entry to distinguish between transaction and tick entries
162pub enum EntryType<Tx: TransactionWithMeta> {
163    Transactions(Vec<Tx>),
164    Tick(Hash),
165}
166
167impl Entry {
168    /// Creates the next Entry `num_hashes` after `start_hash`.
169    pub fn new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec<Transaction>) -> Self {
170        // If you passed in transactions, but passed in num_hashes == 0, then
171        // next_hash will generate the next hash and set num_hashes == 1
172        if num_hashes == 0 && !transactions.is_empty() {
173            num_hashes = 1;
174        }
175
176        let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
177        let hash = next_hash(prev_hash, num_hashes, &transactions);
178        Entry {
179            num_hashes,
180            hash,
181            transactions,
182        }
183    }
184
185    pub fn new_mut(
186        start_hash: &mut Hash,
187        num_hashes: &mut u64,
188        transactions: Vec<Transaction>,
189    ) -> Self {
190        let entry = Self::new(start_hash, *num_hashes, transactions);
191        *start_hash = entry.hash;
192        *num_hashes = 0;
193
194        entry
195    }
196
197    #[cfg(test)]
198    pub fn new_tick(num_hashes: u64, hash: &Hash) -> Self {
199        Entry {
200            num_hashes,
201            hash: *hash,
202            transactions: vec![],
203        }
204    }
205
206    /// Verifies self.hash is the result of hashing a `start_hash` `self.num_hashes` times.
207    /// If the transaction is not a Tick, then hash that as well.
208    pub fn verify(&self, start_hash: &Hash) -> bool {
209        let ref_hash = next_hash(start_hash, self.num_hashes, &self.transactions);
210        if self.hash != ref_hash {
211            warn!(
212                "next_hash is invalid expected: {:?} actual: {:?}",
213                self.hash, ref_hash
214            );
215            return false;
216        }
217        true
218    }
219
220    pub fn is_tick(&self) -> bool {
221        self.transactions.is_empty()
222    }
223}
224
225pub fn hash_transactions(transactions: &[VersionedTransaction]) -> Hash {
226    // a hash of a slice of transactions only needs to hash the signatures
227    let signatures: Vec<_> = transactions
228        .iter()
229        .flat_map(|tx| tx.signatures.iter())
230        .collect();
231    let merkle_tree = MerkleTree::new(&signatures);
232    if let Some(root_hash) = merkle_tree.get_root() {
233        *root_hash
234    } else {
235        Hash::default()
236    }
237}
238
239/// Creates the hash `num_hashes` after `start_hash`. If the transaction contains
240/// a signature, the final hash will be a hash of both the previous ID and
241/// the signature.  If num_hashes is zero and there's no transaction data,
242///  start_hash is returned.
243pub fn next_hash(
244    start_hash: &Hash,
245    num_hashes: u64,
246    transactions: &[VersionedTransaction],
247) -> Hash {
248    if num_hashes == 0 && transactions.is_empty() {
249        return *start_hash;
250    }
251
252    let mut poh = Poh::new(*start_hash, None);
253    poh.hash(num_hashes.saturating_sub(1));
254    if transactions.is_empty() {
255        poh.tick().unwrap().hash
256    } else {
257        poh.record(hash_transactions(transactions)).unwrap().hash
258    }
259}
260
261/// Last action required to verify an entry
262enum VerifyAction {
263    /// Mixin a hash before computing the last hash for a transaction entry
264    Mixin(Hash),
265    /// Compute one last hash for a tick entry
266    Tick,
267    /// No action needed (tick entry with no hashes)
268    None,
269}
270
271pub struct GpuVerificationData {
272    thread_h: Option<JoinHandle<u64>>,
273    hashes: Option<Arc<Mutex<PinnedVec<Hash>>>>,
274    verifications: Option<Vec<(VerifyAction, Hash)>>,
275}
276
277pub enum DeviceVerificationData {
278    Cpu(),
279    Gpu(GpuVerificationData),
280}
281
282pub struct EntryVerificationState {
283    verification_status: EntryVerificationStatus,
284    poh_duration_us: u64,
285    device_verification_data: DeviceVerificationData,
286}
287
288pub struct GpuSigVerificationData {
289    thread_h: Option<JoinHandle<(bool, u64)>>,
290}
291
292pub enum DeviceSigVerificationData {
293    Cpu(),
294    Gpu(GpuSigVerificationData),
295}
296
297pub struct EntrySigVerificationState<Tx: TransactionWithMeta> {
298    verification_status: EntryVerificationStatus,
299    entries: Option<Vec<EntryType<Tx>>>,
300    device_verification_data: DeviceSigVerificationData,
301    gpu_verify_duration_us: u64,
302}
303
304impl<Tx: TransactionWithMeta> EntrySigVerificationState<Tx> {
305    pub fn entries(&mut self) -> Option<Vec<EntryType<Tx>>> {
306        self.entries.take()
307    }
308    pub fn finish_verify(&mut self) -> bool {
309        match &mut self.device_verification_data {
310            DeviceSigVerificationData::Gpu(verification_state) => {
311                let (verified, gpu_time_us) =
312                    verification_state.thread_h.take().unwrap().join().unwrap();
313                self.gpu_verify_duration_us = gpu_time_us;
314                self.verification_status = if verified {
315                    EntryVerificationStatus::Success
316                } else {
317                    EntryVerificationStatus::Failure
318                };
319                verified
320            }
321            DeviceSigVerificationData::Cpu() => {
322                self.verification_status == EntryVerificationStatus::Success
323            }
324        }
325    }
326    pub fn status(&self) -> EntryVerificationStatus {
327        self.verification_status
328    }
329    pub fn gpu_verify_duration(&self) -> u64 {
330        self.gpu_verify_duration_us
331    }
332}
333
334#[derive(Default, Clone)]
335pub struct VerifyRecyclers {
336    hash_recycler: Recycler<PinnedVec<Hash>>,
337    tick_count_recycler: Recycler<PinnedVec<u64>>,
338    packet_recycler: PacketBatchRecycler,
339    out_recycler: Recycler<PinnedVec<u8>>,
340    tx_offset_recycler: Recycler<sigverify::TxOffset>,
341}
342
343#[derive(PartialEq, Eq, Clone, Copy, Debug)]
344pub enum EntryVerificationStatus {
345    Failure,
346    Success,
347    Pending,
348}
349
350impl EntryVerificationState {
351    pub fn status(&self) -> EntryVerificationStatus {
352        self.verification_status
353    }
354
355    pub fn poh_duration_us(&self) -> u64 {
356        self.poh_duration_us
357    }
358
359    pub fn finish_verify(&mut self, thread_pool: &ThreadPool) -> bool {
360        match &mut self.device_verification_data {
361            DeviceVerificationData::Gpu(verification_state) => {
362                let gpu_time_us = verification_state.thread_h.take().unwrap().join().unwrap();
363
364                let mut verify_check_time = Measure::start("verify_check");
365                let hashes = verification_state.hashes.take().unwrap();
366                let hashes = Arc::try_unwrap(hashes)
367                    .expect("unwrap Arc")
368                    .into_inner()
369                    .expect("into_inner");
370                let res = thread_pool.install(|| {
371                    hashes
372                        .into_par_iter()
373                        .cloned()
374                        .zip(verification_state.verifications.take().unwrap())
375                        .all(|(hash, (action, expected))| {
376                            let actual = match action {
377                                VerifyAction::Mixin(mixin) => {
378                                    Poh::new(hash, None).record(mixin).unwrap().hash
379                                }
380                                VerifyAction::Tick => Poh::new(hash, None).tick().unwrap().hash,
381                                VerifyAction::None => hash,
382                            };
383                            actual == expected
384                        })
385                });
386                verify_check_time.stop();
387                self.poh_duration_us += gpu_time_us + verify_check_time.as_us();
388
389                self.verification_status = if res {
390                    EntryVerificationStatus::Success
391                } else {
392                    EntryVerificationStatus::Failure
393                };
394                res
395            }
396            DeviceVerificationData::Cpu() => {
397                self.verification_status == EntryVerificationStatus::Success
398            }
399        }
400    }
401}
402
403pub fn verify_transactions<Tx: TransactionWithMeta + Send + Sync>(
404    entries: Vec<Entry>,
405    thread_pool: &ThreadPool,
406    verify: Arc<dyn Fn(VersionedTransaction) -> Result<Tx> + Send + Sync>,
407) -> Result<Vec<EntryType<Tx>>> {
408    thread_pool.install(|| {
409        entries
410            .into_par_iter()
411            .map(|entry| {
412                if entry.transactions.is_empty() {
413                    Ok(EntryType::Tick(entry.hash))
414                } else {
415                    Ok(EntryType::Transactions(
416                        entry
417                            .transactions
418                            .into_par_iter()
419                            .map(verify.as_ref())
420                            .collect::<Result<Vec<_>>>()?,
421                    ))
422                }
423            })
424            .collect()
425    })
426}
427
428pub fn start_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
429    entries: Vec<Entry>,
430    skip_verification: bool,
431    thread_pool: &ThreadPool,
432    verify_recyclers: VerifyRecyclers,
433    verify: Arc<
434        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
435    >,
436) -> Result<EntrySigVerificationState<Tx>> {
437    let api = perf_libs::api();
438
439    // Use the CPU if we have too few transactions for GPU signature verification to be worth it.
440    // We will also use the CPU if no acceleration API is used or if we're skipping
441    // the signature verification as we'd have nothing to do on the GPU in that case.
442    // TODO: make the CPU-to GPU crossover point dynamic, perhaps based on similar future
443    // heuristics to what might be used in sigverify::ed25519_verify when a dynamic crossover
444    // is introduced for that function (see TODO in sigverify::ed25519_verify)
445    let use_cpu = skip_verification
446        || api.is_none()
447        || entries
448            .iter()
449            .try_fold(0, |accum: usize, entry: &Entry| -> Option<usize> {
450                if accum.saturating_add(entry.transactions.len()) < 512 {
451                    Some(accum.saturating_add(entry.transactions.len()))
452                } else {
453                    None
454                }
455            })
456            .is_some();
457
458    if use_cpu {
459        start_verify_transactions_cpu(entries, skip_verification, thread_pool, verify)
460    } else {
461        start_verify_transactions_gpu(entries, verify_recyclers, thread_pool, verify)
462    }
463}
464
465fn start_verify_transactions_cpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
466    entries: Vec<Entry>,
467    skip_verification: bool,
468    thread_pool: &ThreadPool,
469    verify: Arc<
470        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
471    >,
472) -> Result<EntrySigVerificationState<Tx>> {
473    let verify_func = {
474        let mode = if skip_verification {
475            TransactionVerificationMode::HashOnly
476        } else {
477            TransactionVerificationMode::FullVerification
478        };
479
480        move |versioned_tx| verify(versioned_tx, mode)
481    };
482
483    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
484
485    Ok(EntrySigVerificationState {
486        verification_status: EntryVerificationStatus::Success,
487        entries: Some(entries),
488        device_verification_data: DeviceSigVerificationData::Cpu(),
489        gpu_verify_duration_us: 0,
490    })
491}
492
493fn start_verify_transactions_gpu<Tx: TransactionWithMeta + Send + Sync + 'static>(
494    entries: Vec<Entry>,
495    verify_recyclers: VerifyRecyclers,
496    thread_pool: &ThreadPool,
497    verify: Arc<
498        dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
499    >,
500) -> Result<EntrySigVerificationState<Tx>> {
501    let verify_func = {
502        move |versioned_tx: VersionedTransaction| -> Result<Tx> {
503            verify(versioned_tx, TransactionVerificationMode::HashOnly)
504        }
505    };
506
507    let entries = verify_transactions(entries, thread_pool, Arc::new(verify_func))?;
508
509    let transactions = entries
510        .iter()
511        .filter_map(|entry_type| match entry_type {
512            EntryType::Tick(_) => None,
513            EntryType::Transactions(transactions) => Some(transactions),
514        })
515        .flatten()
516        .collect::<Vec<_>>();
517
518    if transactions.is_empty() {
519        return Ok(EntrySigVerificationState {
520            verification_status: EntryVerificationStatus::Success,
521            entries: Some(entries),
522            device_verification_data: DeviceSigVerificationData::Cpu(),
523            gpu_verify_duration_us: 0,
524        });
525    }
526
527    let packet_batches = thread_pool.install(|| {
528        transactions
529            .par_chunks(PACKETS_PER_BATCH)
530            .map(|transaction_chunk| {
531                let num_transactions = transaction_chunk.len();
532                let mut packet_batch = PinnedPacketBatch::new_with_recycler(
533                    &verify_recyclers.packet_recycler,
534                    num_transactions,
535                    "entry-sig-verify",
536                );
537                // 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(PacketBatch::from(packet_batch))
558                } else {
559                    Err(TransactionError::SanitizeFailure)
560                }
561            })
562            .collect::<Result<Vec<_>>>()
563    });
564    let mut packet_batches = packet_batches?;
565
566    let tx_offset_recycler = verify_recyclers.tx_offset_recycler;
567    let out_recycler = verify_recyclers.out_recycler;
568    let num_packets = transactions.len();
569    let gpu_verify_thread = thread::Builder::new()
570        .name("solGpuSigVerify".into())
571        .spawn(move || {
572            let mut verify_time = Measure::start("sigverify");
573            sigverify::ed25519_verify(
574                &mut packet_batches,
575                &tx_offset_recycler,
576                &out_recycler,
577                false,
578                num_packets,
579            );
580            let verified = packet_batches
581                .iter()
582                .all(|batch| batch.iter().all(|p| !p.meta().discard()));
583            verify_time.stop();
584            (verified, verify_time.as_us())
585        })
586        .unwrap();
587
588    Ok(EntrySigVerificationState {
589        verification_status: EntryVerificationStatus::Pending,
590        entries: Some(entries),
591        device_verification_data: DeviceSigVerificationData::Gpu(GpuSigVerificationData {
592            thread_h: Some(gpu_verify_thread),
593        }),
594        gpu_verify_duration_us: 0,
595    })
596}
597
598fn compare_hashes(computed_hash: Hash, ref_entry: &Entry) -> bool {
599    let actual = if !ref_entry.transactions.is_empty() {
600        let tx_hash = hash_transactions(&ref_entry.transactions);
601        let mut poh = Poh::new(computed_hash, None);
602        poh.record(tx_hash).unwrap().hash
603    } else if ref_entry.num_hashes > 0 {
604        let mut poh = Poh::new(computed_hash, None);
605        poh.tick().unwrap().hash
606    } else {
607        computed_hash
608    };
609    actual == ref_entry.hash
610}
611
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_hash::HASH_BYTES;
693        let now = Instant::now();
694        let genesis = [Entry {
695            num_hashes: 0,
696            hash: *start_hash,
697            transactions: vec![],
698        }];
699
700        let aligned_len = self.len().div_ceil(simd_len) * simd_len;
701        let mut hashes_bytes = vec![0u8; HASH_BYTES * aligned_len];
702        genesis
703            .iter()
704            .chain(self)
705            .enumerate()
706            .for_each(|(i, entry)| {
707                if i < self.len() {
708                    let start = i * HASH_BYTES;
709                    let end = start + HASH_BYTES;
710                    hashes_bytes[start..end].copy_from_slice(&entry.hash.to_bytes());
711                }
712            });
713        let mut hashes_chunked: Vec<_> = hashes_bytes.chunks_mut(simd_len * HASH_BYTES).collect();
714
715        let mut num_hashes: Vec<u64> = self
716            .iter()
717            .map(|entry| entry.num_hashes.saturating_sub(1))
718            .collect();
719        num_hashes.resize(aligned_len, 0);
720        let num_hashes: Vec<_> = num_hashes.chunks(simd_len).collect();
721
722        let res = thread_pool.install(|| {
723            hashes_chunked
724                .par_iter_mut()
725                .zip(num_hashes)
726                .enumerate()
727                .all(|(i, (chunk, num_hashes))| {
728                    match simd_len {
729                        8 => unsafe {
730                            (api().unwrap().poh_verify_many_simd_avx2)(
731                                chunk.as_mut_ptr(),
732                                num_hashes.as_ptr(),
733                            );
734                        },
735                        16 => unsafe {
736                            (api().unwrap().poh_verify_many_simd_avx512skx)(
737                                chunk.as_mut_ptr(),
738                                num_hashes.as_ptr(),
739                            );
740                        },
741                        _ => {
742                            panic!("unsupported simd len: {simd_len}");
743                        }
744                    }
745                    let entry_start = i * simd_len;
746                    // 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 = <[u8; HASH_BYTES]>::try_from(&chunk[start..end])
756                                .map(Hash::new_from_array)
757                                .unwrap();
758                            compare_hashes(hash, ref_entry)
759                        })
760                })
761        });
762        let poh_duration_us = now.elapsed().as_micros() as u64;
763        EntryVerificationState {
764            verification_status: if res {
765                EntryVerificationStatus::Success
766            } else {
767                EntryVerificationStatus::Failure
768            },
769            poh_duration_us,
770            device_verification_data: DeviceVerificationData::Cpu(),
771        }
772    }
773
774    fn verify_cpu(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
775        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
776        let (has_avx2, has_avx512) = (
777            is_x86_feature_detected!("avx2"),
778            is_x86_feature_detected!("avx512f"),
779        );
780        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
781        let (has_avx2, has_avx512) = (false, false);
782
783        if api().is_some() {
784            if has_avx512 && self.len() >= 128 {
785                self.verify_cpu_x86_simd(start_hash, 16, thread_pool)
786            } else if has_avx2 && self.len() >= 48 {
787                self.verify_cpu_x86_simd(start_hash, 8, thread_pool)
788            } else {
789                self.verify_cpu_generic(start_hash, thread_pool)
790            }
791        } else {
792            self.verify_cpu_generic(start_hash, thread_pool)
793        }
794    }
795
796    fn start_verify(
797        &self,
798        start_hash: &Hash,
799        thread_pool: &ThreadPool,
800        recyclers: VerifyRecyclers,
801    ) -> EntryVerificationState {
802        let start = Instant::now();
803        let Some(api) = perf_libs::api() else {
804            return self.verify_cpu(start_hash, thread_pool);
805        };
806        inc_new_counter_info!("entry_verify-num_entries", self.len());
807
808        let genesis = [Entry {
809            num_hashes: 0,
810            hash: *start_hash,
811            transactions: vec![],
812        }];
813
814        let hashes: Vec<Hash> = genesis
815            .iter()
816            .chain(self)
817            .map(|entry| entry.hash)
818            .take(self.len())
819            .collect();
820
821        let mut hashes_pinned = recyclers.hash_recycler.allocate("poh_verify_hash");
822        hashes_pinned.set_pinnable();
823        hashes_pinned.resize(hashes.len(), Hash::default());
824        hashes_pinned.copy_from_slice(&hashes);
825
826        let mut num_hashes_vec = recyclers
827            .tick_count_recycler
828            .allocate("poh_verify_num_hashes");
829        num_hashes_vec.reserve_and_pin(cmp::max(1, self.len()));
830        for entry in self {
831            num_hashes_vec.push(entry.num_hashes.saturating_sub(1));
832        }
833
834        let length = self.len();
835        let hashes = Arc::new(Mutex::new(hashes_pinned));
836        let hashes_clone = hashes.clone();
837
838        let gpu_verify_thread = thread::Builder::new()
839            .name("solGpuPohVerify".into())
840            .spawn(move || {
841                let mut hashes = hashes_clone.lock().unwrap();
842                let gpu_wait = Instant::now();
843                let res;
844                unsafe {
845                    res = (api.poh_verify_many)(
846                        hashes.as_mut_ptr() as *mut u8,
847                        num_hashes_vec.as_ptr(),
848                        length,
849                        1,
850                    );
851                }
852                assert!(res == 0, "GPU PoH verify many failed");
853                inc_new_counter_info!(
854                    "entry_verify-gpu_thread",
855                    gpu_wait.elapsed().as_micros() as usize
856                );
857                gpu_wait.elapsed().as_micros() as u64
858            })
859            .unwrap();
860
861        let verifications = thread_pool.install(|| {
862            self.into_par_iter()
863                .map(|entry| {
864                    let answer = entry.hash;
865                    let action = if entry.transactions.is_empty() {
866                        if entry.num_hashes == 0 {
867                            VerifyAction::None
868                        } else {
869                            VerifyAction::Tick
870                        }
871                    } else {
872                        VerifyAction::Mixin(hash_transactions(&entry.transactions))
873                    };
874                    (action, answer)
875                })
876                .collect()
877        });
878        let device_verification_data = DeviceVerificationData::Gpu(GpuVerificationData {
879            thread_h: Some(gpu_verify_thread),
880            verifications: Some(verifications),
881            hashes: Some(hashes),
882        });
883        EntryVerificationState {
884            verification_status: EntryVerificationStatus::Pending,
885            poh_duration_us: start.elapsed().as_micros() as u64,
886            device_verification_data,
887        }
888    }
889
890    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
891        // When hashes_per_tick is 0, hashing is disabled.
892        if hashes_per_tick == 0 {
893            return true;
894        }
895
896        for entry in self {
897            *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
898            if entry.is_tick() {
899                if *tick_hash_count != hashes_per_tick {
900                    warn!(
901                        "invalid tick hash count!: entry: {entry:#?}, tick_hash_count: \
902                         {tick_hash_count}, hashes_per_tick: {hashes_per_tick}"
903                    );
904                    return false;
905                }
906                *tick_hash_count = 0;
907            }
908        }
909        *tick_hash_count < hashes_per_tick
910    }
911
912    fn tick_count(&self) -> u64 {
913        self.iter().filter(|e| e.is_tick()).count() as u64
914    }
915}
916
917pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
918    let entry = Entry::new(start, num_hashes, transactions);
919    *start = entry.hash;
920    entry
921}
922
923pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
924    repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
925        .take(num_ticks as usize)
926        .collect()
927}
928
929pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
930    repeat_with(|| {
931        let hashes_per_tick = thread_rng().gen_range(1..max_hashes_per_tick);
932        next_entry_mut(&mut hash, hashes_per_tick, vec![])
933    })
934    .take(num_ticks as usize)
935    .collect()
936}
937
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
970#[cfg(feature = "dev-context-only-utils")]
971pub fn thread_pool_for_benches() -> ThreadPool {
972    rayon::ThreadPoolBuilder::new()
973        .num_threads(num_cpus::get())
974        .thread_name(|i| format!("solEntryBnch{i:02}"))
975        .build()
976        .expect("new rayon threadpool")
977}
978
979#[cfg(test)]
980mod tests {
981    use {
982        super::*,
983        agave_reserved_account_keys::ReservedAccountKeys,
984        solana_hash::Hash,
985        solana_keypair::Keypair,
986        solana_message::SimpleAddressLoader,
987        solana_perf::test_tx::{test_invalid_tx, test_tx},
988        solana_pubkey::Pubkey,
989        solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
990        solana_sha256_hasher::hash,
991        solana_signer::Signer,
992        solana_system_transaction as system_transaction,
993        solana_transaction::{
994            sanitized::{MessageHash, SanitizedTransaction},
995            versioned::VersionedTransaction,
996        },
997        solana_transaction_error::TransactionResult as Result,
998    };
999
1000    #[test]
1001    fn test_entry_verify() {
1002        let zero = Hash::default();
1003        let one = hash(zero.as_ref());
1004        assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case, never used
1005        assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
1006        assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step
1007        assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad
1008    }
1009
1010    fn test_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
1011        entries: Vec<Entry>,
1012        skip_verification: bool,
1013        verify_recyclers: VerifyRecyclers,
1014        thread_pool: &ThreadPool,
1015        verify: Arc<
1016            dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result<Tx> + Send + Sync,
1017        >,
1018    ) -> bool {
1019        let verify_func = {
1020            let verify = verify.clone();
1021            let verification_mode = if skip_verification {
1022                TransactionVerificationMode::HashOnly
1023            } else {
1024                TransactionVerificationMode::FullVerification
1025            };
1026            move |versioned_tx: VersionedTransaction| -> Result<Tx> {
1027                verify(versioned_tx, verification_mode)
1028            }
1029        };
1030
1031        let cpu_verify_result =
1032            verify_transactions(entries.clone(), thread_pool, Arc::new(verify_func));
1033        let mut gpu_verify_result: EntrySigVerificationState<Tx> = {
1034            let verify_result = start_verify_transactions(
1035                entries,
1036                skip_verification,
1037                thread_pool,
1038                verify_recyclers,
1039                verify,
1040            );
1041            match verify_result {
1042                Ok(res) => res,
1043                _ => EntrySigVerificationState {
1044                    verification_status: EntryVerificationStatus::Failure,
1045                    entries: None,
1046                    device_verification_data: DeviceSigVerificationData::Cpu(),
1047                    gpu_verify_duration_us: 0,
1048                },
1049            }
1050        };
1051
1052        match cpu_verify_result {
1053            Ok(_) => {
1054                assert!(gpu_verify_result.verification_status != EntryVerificationStatus::Failure);
1055                assert!(gpu_verify_result.finish_verify());
1056                true
1057            }
1058            _ => {
1059                assert!(
1060                    gpu_verify_result.verification_status == EntryVerificationStatus::Failure
1061                        || !gpu_verify_result.finish_verify()
1062                );
1063                false
1064            }
1065        }
1066    }
1067
1068    #[test]
1069    fn test_entry_gpu_verify() {
1070        let thread_pool = thread_pool_for_tests();
1071
1072        let verify_transaction = {
1073            move |versioned_tx: VersionedTransaction,
1074                  verification_mode: TransactionVerificationMode|
1075                  -> Result<RuntimeTransaction<SanitizedTransaction>> {
1076                let sanitized_tx = {
1077                    let message_hash =
1078                        if verification_mode == TransactionVerificationMode::FullVerification {
1079                            versioned_tx.verify_and_hash_message()?
1080                        } else {
1081                            versioned_tx.message.hash()
1082                        };
1083
1084                    RuntimeTransaction::try_create(
1085                        versioned_tx,
1086                        MessageHash::Precomputed(message_hash),
1087                        None,
1088                        SimpleAddressLoader::Disabled,
1089                        &ReservedAccountKeys::empty_key_set(),
1090                        true,
1091                    )
1092                }?;
1093
1094                Ok(sanitized_tx)
1095            }
1096        };
1097
1098        let recycler = VerifyRecyclers::default();
1099
1100        // Make sure we test with a number of transactions that's not a multiple of PACKETS_PER_BATCH
1101        let entries_invalid = (0..1025)
1102            .map(|_| {
1103                let transaction = test_invalid_tx();
1104                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1105            })
1106            .collect::<Vec<_>>();
1107
1108        let entries_valid = (0..1025)
1109            .map(|_| {
1110                let transaction = test_tx();
1111                next_entry_mut(&mut Hash::default(), 0, vec![transaction])
1112            })
1113            .collect::<Vec<_>>();
1114
1115        assert!(!test_verify_transactions(
1116            entries_invalid,
1117            false,
1118            recycler.clone(),
1119            &thread_pool,
1120            Arc::new(verify_transaction)
1121        ));
1122        assert!(test_verify_transactions(
1123            entries_valid,
1124            false,
1125            recycler,
1126            &thread_pool,
1127            Arc::new(verify_transaction)
1128        ));
1129    }
1130
1131    #[test]
1132    fn test_transaction_reorder_attack() {
1133        let zero = Hash::default();
1134
1135        // First, verify entries
1136        let keypair = Keypair::new();
1137        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1138        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1139        let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
1140        assert!(e0.verify(&zero));
1141
1142        // Next, swap two transactions and ensure verification fails.
1143        e0.transactions[0] = tx1.into(); // <-- attack
1144        e0.transactions[1] = tx0.into();
1145        assert!(!e0.verify(&zero));
1146    }
1147
1148    #[test]
1149    fn test_transaction_signing() {
1150        let thread_pool = thread_pool_for_tests();
1151
1152        use solana_signature::Signature;
1153        let zero = Hash::default();
1154
1155        let keypair = Keypair::new();
1156        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1157        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
1158
1159        // Verify entry with 2 transactions
1160        let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
1161        assert!(e0.verify(&zero, &thread_pool));
1162
1163        // Clear signature of the first transaction, see that it does not verify
1164        let orig_sig = e0[0].transactions[0].signatures[0];
1165        e0[0].transactions[0].signatures[0] = Signature::default();
1166        assert!(!e0.verify(&zero, &thread_pool));
1167
1168        // restore original signature
1169        e0[0].transactions[0].signatures[0] = orig_sig;
1170        assert!(e0.verify(&zero, &thread_pool));
1171
1172        // Resize signatures and see verification fails.
1173        let len = e0[0].transactions[0].signatures.len();
1174        e0[0].transactions[0]
1175            .signatures
1176            .resize(len - 1, Signature::default());
1177        assert!(!e0.verify(&zero, &thread_pool));
1178
1179        // Pass an entry with no transactions
1180        let e0 = [Entry::new(&zero, 0, vec![])];
1181        assert!(e0.verify(&zero, &thread_pool));
1182    }
1183
1184    #[test]
1185    fn test_next_entry() {
1186        let zero = Hash::default();
1187        let tick = next_entry(&zero, 1, vec![]);
1188        assert_eq!(tick.num_hashes, 1);
1189        assert_ne!(tick.hash, zero);
1190
1191        let tick = next_entry(&zero, 0, vec![]);
1192        assert_eq!(tick.num_hashes, 0);
1193        assert_eq!(tick.hash, zero);
1194
1195        let keypair = Keypair::new();
1196        let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1197        let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1198        assert_eq!(entry0.num_hashes, 1);
1199        assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1200    }
1201
1202    #[test]
1203    #[should_panic]
1204    fn test_next_entry_panic() {
1205        let zero = Hash::default();
1206        let keypair = Keypair::new();
1207        let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1208        next_entry(&zero, 0, vec![tx]);
1209    }
1210
1211    #[test]
1212    fn test_verify_slice1() {
1213        agave_logger::setup();
1214        let thread_pool = thread_pool_for_tests();
1215
1216        let zero = Hash::default();
1217        let one = hash(zero.as_ref());
1218        // base case
1219        assert!(vec![][..].verify(&zero, &thread_pool));
1220        // singleton case 1
1221        assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero, &thread_pool));
1222        // singleton case 2, bad
1223        assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one, &thread_pool));
1224        // inductive step
1225        assert!(vec![next_entry(&zero, 0, vec![]); 2][..].verify(&zero, &thread_pool));
1226
1227        let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1228        bad_ticks[1].hash = one;
1229        // inductive step, bad
1230        assert!(!bad_ticks.verify(&zero, &thread_pool));
1231    }
1232
1233    #[test]
1234    fn test_verify_slice_with_hashes1() {
1235        agave_logger::setup();
1236        let thread_pool = thread_pool_for_tests();
1237
1238        let zero = Hash::default();
1239        let one = hash(zero.as_ref());
1240        let two = hash(one.as_ref());
1241        // base case
1242        assert!(vec![][..].verify(&one, &thread_pool));
1243        // singleton case 1
1244        assert!(vec![Entry::new_tick(1, &two)][..].verify(&one, &thread_pool));
1245        // singleton case 2, bad
1246        assert!(!vec![Entry::new_tick(1, &two)][..].verify(&two, &thread_pool));
1247
1248        let mut ticks = vec![next_entry(&one, 1, vec![])];
1249        ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1250        // inductive step
1251        assert!(ticks.verify(&one, &thread_pool));
1252
1253        let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1254        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1255        bad_ticks[1].hash = one;
1256        // inductive step, bad
1257        assert!(!bad_ticks.verify(&one, &thread_pool));
1258    }
1259
1260    #[test]
1261    fn test_verify_slice_with_hashes_and_transactions() {
1262        agave_logger::setup();
1263        let thread_pool = thread_pool_for_tests();
1264
1265        let zero = Hash::default();
1266        let one = hash(zero.as_ref());
1267        let two = hash(one.as_ref());
1268        let alice_keypair = Keypair::new();
1269        let bob_keypair = Keypair::new();
1270        let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1271        let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1272        // base case
1273        assert!(vec![][..].verify(&one, &thread_pool));
1274        // singleton case 1
1275        assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one, &thread_pool));
1276        // singleton case 2, bad
1277        assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two, &thread_pool));
1278
1279        let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1280        ticks.push(next_entry(
1281            &ticks.last().unwrap().hash,
1282            1,
1283            vec![tx1.clone()],
1284        ));
1285
1286        // inductive step
1287        assert!(ticks.verify(&one, &thread_pool));
1288
1289        let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1290        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1291        bad_ticks[1].hash = one;
1292        // inductive step, bad
1293        assert!(!bad_ticks.verify(&one, &thread_pool));
1294    }
1295
1296    #[test]
1297    fn test_verify_tick_hash_count() {
1298        let hashes_per_tick = 10;
1299        let tx = VersionedTransaction::default();
1300
1301        let no_hash_tx_entry = Entry {
1302            transactions: vec![tx.clone()],
1303            ..Entry::default()
1304        };
1305        let single_hash_tx_entry = Entry {
1306            transactions: vec![tx.clone()],
1307            num_hashes: 1,
1308            ..Entry::default()
1309        };
1310        let partial_tx_entry = Entry {
1311            num_hashes: hashes_per_tick - 1,
1312            transactions: vec![tx.clone()],
1313            ..Entry::default()
1314        };
1315        let full_tx_entry = Entry {
1316            num_hashes: hashes_per_tick,
1317            transactions: vec![tx.clone()],
1318            ..Entry::default()
1319        };
1320        let max_hash_tx_entry = Entry {
1321            transactions: vec![tx],
1322            num_hashes: u64::MAX,
1323            ..Entry::default()
1324        };
1325
1326        let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1327        let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1328        let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1329        let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1330        let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1331
1332        // empty batch should succeed if hashes_per_tick hasn't been reached
1333        let mut tick_hash_count = 0;
1334        let mut entries = vec![];
1335        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1336        assert_eq!(tick_hash_count, 0);
1337
1338        // empty batch should fail if hashes_per_tick has been reached
1339        tick_hash_count = hashes_per_tick;
1340        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1341        assert_eq!(tick_hash_count, hashes_per_tick);
1342        tick_hash_count = 0;
1343
1344        // validation is disabled when hashes_per_tick == 0
1345        entries = vec![max_hash_tx_entry.clone()];
1346        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1347        assert_eq!(tick_hash_count, 0);
1348
1349        // partial tick should fail
1350        entries = vec![partial_tick_entry.clone()];
1351        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1352        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1353        tick_hash_count = 0;
1354
1355        // full tick entry should succeed
1356        entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1357        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1358        assert_eq!(tick_hash_count, 0);
1359
1360        // oversized tick entry should fail
1361        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1362        assert_eq!(tick_hash_count, hashes_per_tick);
1363        tick_hash_count = 0;
1364
1365        // partial tx entry without tick entry should succeed
1366        entries = vec![partial_tx_entry];
1367        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1368        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1369        tick_hash_count = 0;
1370
1371        // full tx entry with tick entry should succeed
1372        entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1373        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1374        assert_eq!(tick_hash_count, 0);
1375
1376        // full tx entry with oversized tick entry should fail
1377        entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1378        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1379        assert_eq!(tick_hash_count, hashes_per_tick + 1);
1380        tick_hash_count = 0;
1381
1382        // full tx entry without tick entry should fail
1383        entries = vec![full_tx_entry];
1384        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1385        assert_eq!(tick_hash_count, hashes_per_tick);
1386        tick_hash_count = 0;
1387
1388        // tx entry and a tick should succeed
1389        entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1390        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1391        assert_eq!(tick_hash_count, 0);
1392
1393        // many tx entries and a tick should succeed
1394        let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1395            .map(|_| single_hash_tx_entry.clone())
1396            .collect();
1397        entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1398        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1399        assert_eq!(tick_hash_count, 0);
1400
1401        // check overflow saturation should fail
1402        entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1403        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1404        assert_eq!(tick_hash_count, u64::MAX);
1405        tick_hash_count = 0;
1406
1407        // check overflow saturation should fail
1408        entries = vec![max_hash_tx_entry, full_tick_entry];
1409        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1410        assert_eq!(tick_hash_count, u64::MAX);
1411    }
1412
1413    #[test]
1414    fn test_poh_verify_fuzz() {
1415        agave_logger::setup();
1416        for _ in 0..100 {
1417            let mut time = Measure::start("ticks");
1418            let num_ticks = thread_rng().gen_range(1..100);
1419            info!("create {num_ticks} ticks:");
1420            let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1421            time.stop();
1422
1423            let mut modified = false;
1424            if thread_rng().gen_ratio(1, 2) {
1425                modified = true;
1426                let modify_idx = thread_rng().gen_range(0..num_ticks) as usize;
1427                entries[modify_idx].hash = hash(&[1, 2, 3]);
1428            }
1429
1430            info!("done.. {time}");
1431            let mut time = Measure::start("poh");
1432            let res = entries.verify(&Hash::default(), &thread_pool_for_tests());
1433            assert_eq!(res, !modified);
1434            time.stop();
1435            info!("{time} {res}");
1436        }
1437    }
1438
1439    #[test]
1440    fn test_hash_transactions() {
1441        let mut transactions: Vec<_> = [test_tx(), test_tx(), test_tx()]
1442            .into_iter()
1443            .map(VersionedTransaction::from)
1444            .collect();
1445
1446        // Test different permutations of the transactions have different final hashes.
1447        // i.e. that **order** of transactions is included in the hash.
1448        let hash1 = hash_transactions(&transactions);
1449        transactions.swap(0, 1);
1450        let hash2 = hash_transactions(&transactions);
1451        assert_ne!(hash1, hash2);
1452    }
1453}