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    agave_transaction_view::{
8        transaction_data::TransactionData, transaction_view::UnsanitizedTransactionView,
9    },
10    crossbeam_channel::{Receiver, Sender},
11    log::*,
12    rayon::{ThreadPool, prelude::*},
13    smallvec::SmallVec,
14    solana_address::Address,
15    solana_cost_model::shred_limit::DEFAULT_MAX_DATA_SHREDS_PER_SLOT,
16    solana_hash::Hash,
17    solana_merkle_tree::MerkleTree,
18    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
19    solana_signature::Signature,
20    solana_transaction::{Transaction, versioned::VersionedTransaction},
21    solana_transaction_error::{TransactionError, TransactionResult as Result},
22    std::{fmt::Formatter, iter::repeat_with, time::Instant},
23    wincode::{SchemaRead, SchemaWrite, containers::Vec as WincodeVec, len::BincodeLen},
24};
25
26pub type EntrySender = Sender<Vec<Entry>>;
27pub type EntryReceiver = Receiver<Vec<Entry>>;
28
29pub const MAX_DATA_SHREDS_SIZE: usize =
30    DEFAULT_MAX_DATA_SHREDS_PER_SLOT as usize * solana_packet::PACKET_DATA_SIZE;
31pub type MaxDataShredsLen = BincodeLen<MAX_DATA_SHREDS_SIZE>;
32
33/// Each Entry contains three pieces of data. The `num_hashes` field is the number
34/// of hashes performed since the previous entry.  The `hash` field is the result
35/// of hashing `hash` from the previous entry `num_hashes` times.  The `transactions`
36/// field points to Transactions that took place shortly before `hash` was generated.
37///
38/// If you multiply `num_hashes` by the amount of time it takes to generate a new hash, you
39/// get a duration estimate since the last `Entry`. Since processing power increases
40/// over time, one should expect the duration `num_hashes` represents to decrease proportionally.
41/// An upper bound on Duration can be estimated by assuming each hash was generated by the
42/// world's fastest processor at the time the entry was recorded. Or said another way, it
43/// is physically not possible for a shorter duration to have occurred if one assumes the
44/// hash was computed by the world's fastest processor at that time. The hash chain is both
45/// a Verifiable Delay Function (VDF) and a Proof of Work (not to be confused with Proof of
46/// Work consensus!)
47#[derive(Debug, Default, PartialEq, Eq, Clone, SchemaWrite, SchemaRead)]
48pub struct Entry {
49    /// The number of hashes since the previous Entry ID.
50    pub num_hashes: u64,
51
52    /// The SHA-256 hash `num_hashes` after the previous Entry ID.
53    pub hash: Hash,
54
55    /// An ordered list of transactions that were observed before the Entry ID was
56    /// generated. They may have been observed before a previous Entry ID but were
57    /// pushed back into this list to ensure deterministic interpretation of the ledger.
58    #[wincode(with = "WincodeVec<VersionedTransaction, MaxDataShredsLen>")]
59    pub transactions: Vec<VersionedTransaction>,
60}
61
62pub struct EntryView<D: TransactionData> {
63    /// The number of hashes since the previous Entry ID.
64    pub num_hashes: u64,
65
66    /// The SHA-256 hash `num_hashes` after the previous Entry ID.
67    pub hash: Hash,
68
69    /// An ordered list of transactions that were observed before the Entry ID was
70    /// generated. They may have been observed before a previous Entry ID but were
71    /// pushed back into this list to ensure deterministic interpretation of the ledger.
72    pub transactions: Vec<UnsanitizedTransactionView<D>>,
73}
74
75// for tests only, as this is an expensive operation and not needed in production.
76#[cfg(feature = "dev-context-only-utils")]
77impl<D> PartialEq for EntryView<D>
78where
79    D: PartialEq + TransactionData,
80{
81    fn eq(&self, other: &Self) -> bool {
82        let same_num_hashes = self.num_hashes == other.num_hashes;
83        let same_hash = self.hash == other.hash;
84        let same_transactions = || {
85            let same_len = self.transactions.len() == other.transactions.len();
86
87            if !same_len {
88                return false;
89            }
90
91            self.transactions
92                .iter()
93                .zip(other.transactions.iter())
94                .all(|(tx1, tx2)| tx1.data() == tx2.data())
95        };
96
97        same_num_hashes && same_hash && same_transactions()
98    }
99}
100
101impl<D> std::fmt::Debug for EntryView<D>
102where
103    D: TransactionData,
104{
105    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
106        f.debug_struct("EntryView")
107            .field("num_hashes", &self.num_hashes)
108            .field("hash", &self.hash)
109            .field("transactions", &self.transactions)
110            .finish()
111    }
112}
113
114impl<D: TransactionData> EntryView<D> {
115    pub fn is_tick(&self) -> bool {
116        self.transactions.is_empty()
117    }
118}
119
120/// Serializes `entries` and re-parses them through [`crate::block_component_parser::parse`] to
121/// get byte-backed [`EntryView`]s, reusing the same parsing path production uses rather than
122/// hand-constructing transaction views.
123#[cfg(feature = "dev-context-only-utils")]
124pub fn entry_views_for_tests(entries: Vec<Entry>) -> Vec<EntryView<bytes::Bytes>> {
125    let component = crate::block_component::BlockComponent::new_entry_batch(entries).unwrap();
126    let bytes = wincode::serialize(&component).unwrap();
127    let crate::block_component::ParsedBlockComponent::EntryBatch(views) =
128        crate::block_component_parser::parse(bytes).unwrap()
129    else {
130        panic!("expected EntryBatch");
131    };
132    views
133}
134
135// The data needed to verify an Entry.
136#[derive(Clone, Debug, Default, PartialEq, Eq)]
137pub struct EntryVerificationData {
138    pub num_hashes: u64,
139    pub hash: Hash,
140    pub num_transactions: usize,
141    pub signatures: Vec<Signature>,
142}
143
144impl From<&Entry> for EntryVerificationData {
145    fn from(entry: &Entry) -> Self {
146        Self {
147            num_hashes: entry.num_hashes,
148            hash: entry.hash,
149            num_transactions: entry.transactions.len(),
150            signatures: entry
151                .transactions
152                .iter()
153                .flat_map(|tx| tx.signatures.iter().copied())
154                .collect(),
155        }
156    }
157}
158
159impl<D: TransactionData> From<&EntryView<D>> for EntryVerificationData {
160    fn from(entry: &EntryView<D>) -> Self {
161        Self {
162            num_hashes: entry.num_hashes,
163            hash: entry.hash,
164            num_transactions: entry.transactions.len(),
165            signatures: entry
166                .transactions
167                .iter()
168                .flat_map(|tx| tx.signatures().iter().copied())
169                .collect(),
170        }
171    }
172}
173
174impl EntryVerificationData {
175    pub fn verify(&self, start_hash: &Hash) -> bool {
176        let ref_hash = next_hash_with_signatures(
177            start_hash,
178            self.num_hashes,
179            self.num_transactions,
180            &self.signatures,
181        );
182        if self.hash != ref_hash {
183            warn!(
184                "next_hash is invalid expected: {:?} actual: {:?}",
185                self.hash, ref_hash
186            );
187            return false;
188        }
189        true
190    }
191}
192
193pub fn entries_to_verification_data(entries: &[Entry]) -> Vec<EntryVerificationData> {
194    entries.iter().map(Into::into).collect()
195}
196
197pub fn entry_views_to_verification_data<D: TransactionData>(
198    entries: &[EntryView<D>],
199) -> Vec<EntryVerificationData> {
200    entries.iter().map(Into::into).collect()
201}
202
203pub struct EntrySummary {
204    pub num_hashes: u64,
205    pub hash: Hash,
206    pub num_transactions: u64,
207}
208
209impl From<&Entry> for EntrySummary {
210    fn from(entry: &Entry) -> Self {
211        Self {
212            num_hashes: entry.num_hashes,
213            hash: entry.hash,
214            num_transactions: entry.transactions.len() as u64,
215        }
216    }
217}
218
219impl<D: TransactionData> From<&EntryView<D>> for EntrySummary {
220    fn from(entry: &EntryView<D>) -> Self {
221        Self {
222            num_hashes: entry.num_hashes,
223            hash: entry.hash,
224            num_transactions: entry.transactions.len() as u64,
225        }
226    }
227}
228
229/// Typed entry to distinguish between transaction and tick entries
230pub enum EntryType<Tx: TransactionWithMeta> {
231    Transactions(Vec<Tx>),
232    Tick(Hash),
233}
234
235#[derive(Debug)]
236struct TxVerificationData<D: TransactionData> {
237    is_simple_vote: bool,
238    signatures: SmallVec<[Signature; 2]>,
239    signer_pubkeys: SmallVec<[Address; 2]>,
240    message_hash: Hash,
241    transaction_view: UnsanitizedTransactionView<D>,
242}
243
244pub struct UnverifiedSignatures<D: TransactionData> {
245    signatures: Vec<TxVerificationData<D>>,
246}
247
248impl<D> UnverifiedSignatures<D>
249where
250    D: TransactionData + Send + Sync,
251{
252    fn with_capacity(capacity: usize) -> Self {
253        Self {
254            signatures: Vec::with_capacity(capacity),
255        }
256    }
257
258    /// Verifies all collected signatures in parallel on the current rayon
259    /// thread pool, checking each signature individually.
260    pub fn verify(&self) -> Result<()> {
261        let all_valid = self
262            .signatures
263            .par_iter()
264            .flat_map_iter(|tx| {
265                let message = tx.transaction_view.message_data();
266                tx.signatures
267                    .iter()
268                    .zip(tx.signer_pubkeys.iter())
269                    .map(move |(signature, pubkey)| (signature, pubkey, message))
270            })
271            .all(|(signature, pubkey, message)| signature.verify(pubkey.as_ref(), message));
272
273        if all_valid {
274            Ok(())
275        } else {
276            Err(TransactionError::SignatureFailure)
277        }
278    }
279
280    #[cfg(feature = "dev-context-only-utils")]
281    /// Per-signature verification baseline, kept only for benchmarking against
282    /// [`Self::verify`].
283    pub fn verify_single_loop_for_benches(&self) -> Result<()> {
284        self.signatures.par_iter().try_for_each(|tx_signatures| {
285            if tx_signatures
286                .signatures
287                .iter()
288                .zip(tx_signatures.signer_pubkeys.iter())
289                .all(|(signature, pubkey)| {
290                    signature.verify(
291                        pubkey.as_ref(),
292                        tx_signatures.transaction_view.message_data(),
293                    )
294                })
295            {
296                Ok(())
297            } else {
298                Err(TransactionError::SignatureFailure)
299            }
300        })
301    }
302
303    pub fn len(&self) -> usize {
304        self.signatures.len()
305    }
306
307    pub fn is_empty(&self) -> bool {
308        self.signatures.is_empty()
309    }
310
311    pub fn verify_signatures(&self, index: usize) -> bool {
312        let tx_data = &self.signatures[index];
313        let message = tx_data.transaction_view.message_data();
314        tx_data
315            .signatures
316            .iter()
317            .zip(&tx_data.signer_pubkeys)
318            .all(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message))
319    }
320
321    pub fn vote_transaction_message_hash(&self, index: usize) -> Option<Hash> {
322        let tx_data = &self.signatures[index];
323        (tx_data.is_simple_vote && !tx_data.signatures.is_empty()).then_some(tx_data.message_hash)
324    }
325
326    pub fn vote_transaction_message_hashes(&self) -> Vec<Hash> {
327        self.signatures
328            .iter()
329            .filter(|tx_signatures| tx_signatures.is_simple_vote)
330            .filter_map(|tx_signatures| {
331                tx_signatures
332                    .signatures
333                    .first()
334                    .map(|_| tx_signatures.message_hash)
335            })
336            .collect()
337    }
338}
339
340pub struct ValidatedHashedTransactions<Tx: TransactionWithMeta, D: TransactionData> {
341    pub entries: Vec<EntryType<Tx>>,
342    pub unverified_signatures: UnverifiedSignatures<D>,
343}
344
345impl Entry {
346    /// Creates the next Entry `num_hashes` after `start_hash`.
347    pub fn new(prev_hash: &Hash, mut num_hashes: u64, transactions: Vec<Transaction>) -> Self {
348        // If you passed in transactions, but passed in num_hashes == 0, then
349        // next_hash will generate the next hash and set num_hashes == 1
350        if num_hashes == 0 && !transactions.is_empty() {
351            num_hashes = 1;
352        }
353
354        let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
355        let hash = next_hash(prev_hash, num_hashes, &transactions);
356        Entry {
357            num_hashes,
358            hash,
359            transactions,
360        }
361    }
362
363    pub fn new_mut(
364        start_hash: &mut Hash,
365        num_hashes: &mut u64,
366        transactions: Vec<Transaction>,
367    ) -> Self {
368        let entry = Self::new(start_hash, *num_hashes, transactions);
369        *start_hash = entry.hash;
370        *num_hashes = 0;
371
372        entry
373    }
374
375    #[cfg(test)]
376    pub fn new_tick(num_hashes: u64, hash: &Hash) -> Self {
377        Entry {
378            num_hashes,
379            hash: *hash,
380            transactions: vec![],
381        }
382    }
383
384    /// Verifies self.hash is the result of hashing a `start_hash` `self.num_hashes` times.
385    /// If the transaction is not a Tick, then hash that as well.
386    pub fn verify(&self, start_hash: &Hash) -> bool {
387        EntryVerificationData::from(self).verify(start_hash)
388    }
389
390    pub fn is_tick(&self) -> bool {
391        self.transactions.is_empty()
392    }
393}
394
395pub fn hash_signatures(signatures: &[impl AsRef<[u8]>]) -> Hash {
396    let merkle_tree = MerkleTree::new(signatures);
397    if let Some(root_hash) = merkle_tree.get_root() {
398        *root_hash
399    } else {
400        Hash::default()
401    }
402}
403
404pub fn hash_transactions(transactions: &[VersionedTransaction]) -> Hash {
405    // a hash of a slice of transactions only needs to hash the signatures
406    let signatures: Vec<_> = transactions
407        .iter()
408        .flat_map(|tx| tx.signatures.iter())
409        .collect();
410    hash_signatures(&signatures)
411}
412
413fn next_hash_with_signatures(
414    start_hash: &Hash,
415    num_hashes: u64,
416    num_transactions: usize,
417    signatures: &[Signature],
418) -> Hash {
419    if num_hashes == 0 && num_transactions == 0 {
420        return *start_hash;
421    }
422
423    let mut poh = Poh::new(*start_hash, None);
424    poh.hash(num_hashes.saturating_sub(1));
425    if num_transactions == 0 {
426        poh.tick().unwrap().hash
427    } else {
428        poh.record(hash_signatures(signatures)).unwrap().hash
429    }
430}
431
432/// Creates the hash `num_hashes` after `start_hash`. If the transaction contains a signature, the
433/// final hash will be a hash of both the previous ID and the signature.  If num_hashes is zero and
434/// there's no transaction data, start_hash is returned.
435pub fn next_hash(
436    start_hash: &Hash,
437    num_hashes: u64,
438    transactions: &[VersionedTransaction],
439) -> Hash {
440    let signatures: Vec<_> = transactions
441        .iter()
442        .flat_map(|tx| tx.signatures.iter().copied())
443        .collect();
444    next_hash_with_signatures(start_hash, num_hashes, transactions.len(), &signatures)
445}
446
447pub struct EntryVerificationState {
448    verification_status: bool,
449    poh_duration_us: u64,
450}
451
452impl EntryVerificationState {
453    pub fn status(&self) -> bool {
454        self.verification_status
455    }
456
457    pub fn poh_duration_us(&self) -> u64 {
458        self.poh_duration_us
459    }
460}
461
462fn validate_and_hash_entry_transactions<Tx: TransactionWithMeta, F, D>(
463    entry: EntryView<D>,
464    verify: &F,
465    unverified_signatures: &mut UnverifiedSignatures<D>,
466) -> Result<EntryType<Tx>>
467where
468    F: Fn(UnsanitizedTransactionView<D>) -> Result<Tx>,
469    D: TransactionData + Clone,
470{
471    if entry.transactions.is_empty() {
472        return Ok(EntryType::Tick(entry.hash));
473    }
474
475    let verified_transactions = entry
476        .transactions
477        .into_iter()
478        .map(|transaction_view| {
479            let num_signers: usize = transaction_view.num_required_signatures().into();
480            let static_account_keys = transaction_view.static_account_keys();
481            if static_account_keys.len() < num_signers {
482                return Err(TransactionError::SanitizeFailure);
483            }
484            let signatures = transaction_view.signatures().iter().copied().collect();
485            let signer_pubkeys = static_account_keys[..num_signers].iter().copied().collect();
486            let verified_transaction = verify(transaction_view.clone())?;
487            let message_hash = *verified_transaction.message_hash();
488            unverified_signatures.signatures.push(TxVerificationData {
489                is_simple_vote: verified_transaction.is_simple_vote_transaction(),
490                signatures,
491                message_hash,
492                transaction_view,
493                signer_pubkeys,
494            });
495
496            Ok(verified_transaction)
497        })
498        .collect::<Result<Vec<_>>>()?;
499    Ok(EntryType::Transactions(verified_transactions))
500}
501
502/// Validates and hashes the transactions included in the given entries.
503///
504/// The function does NOT verify transaction signatures. The caller is expected to call
505/// `UnverifiedSignatures::verify()` on the returned `unverified_signatures`.
506pub fn validate_and_hash_transactions<Tx, F, D>(
507    entries: Vec<EntryView<D>>,
508    num_txs: usize,
509    thread_pool: &ThreadPool,
510    verify: F,
511) -> Result<ValidatedHashedTransactions<Tx, D>>
512where
513    Tx: TransactionWithMeta + Send + Sync,
514    F: Fn(UnsanitizedTransactionView<D>) -> Result<Tx> + Send + Sync,
515    D: TransactionData + Clone + Send + Sync,
516{
517    const PARALLEL_VERIFY_THRESHOLD: usize = 200;
518    if num_txs < PARALLEL_VERIFY_THRESHOLD {
519        let mut unverified_signatures = UnverifiedSignatures::with_capacity(num_txs);
520        let entries = entries
521            .into_iter()
522            .map(|entry| {
523                validate_and_hash_entry_transactions(entry, &verify, &mut unverified_signatures)
524            })
525            .collect::<Result<_>>()?;
526        return Ok(ValidatedHashedTransactions {
527            entries,
528            unverified_signatures,
529        });
530    }
531
532    let verified = thread_pool.install(|| {
533        entries
534            .into_par_iter()
535            .map(|entry| {
536                let mut unverified_signatures =
537                    UnverifiedSignatures::with_capacity(entry.transactions.len());
538                let verified_entry = validate_and_hash_entry_transactions(
539                    entry,
540                    &verify,
541                    &mut unverified_signatures,
542                )?;
543                Ok((verified_entry, unverified_signatures))
544            })
545            .collect::<Result<Vec<_>>>()
546    })?;
547
548    let mut entries = Vec::with_capacity(verified.len());
549    let mut unverified_signatures = UnverifiedSignatures::with_capacity(num_txs);
550    for (entry, mut tx_unverified_signatures) in verified {
551        entries.push(entry);
552        unverified_signatures
553            .signatures
554            .append(&mut tx_unverified_signatures.signatures);
555    }
556    Ok(ValidatedHashedTransactions {
557        entries,
558        unverified_signatures,
559    })
560}
561
562pub fn verify_entries_cpu_in_pool(
563    entries: &[EntryVerificationData],
564    start_hash: &Hash,
565    thread_pool: &ThreadPool,
566) -> EntryVerificationState {
567    thread_pool.install(|| verify_entries_cpu(entries, start_hash))
568}
569
570fn verify_entries_cpu_generic(
571    entries: &[EntryVerificationData],
572    start_hash: &Hash,
573) -> EntryVerificationState {
574    let now = Instant::now();
575    let genesis = [EntryVerificationData {
576        num_hashes: 0,
577        hash: *start_hash,
578        num_transactions: 0,
579        signatures: Vec::new(),
580    }];
581    let entry_pairs = genesis.par_iter().chain(entries).zip(entries);
582    let res = entry_pairs.all(|(x0, x1)| {
583        let r = x1.verify(&x0.hash);
584        if !r {
585            warn!(
586                "entry invalid!: x0: {:?}, x1: {:?} num txs: {}",
587                x0.hash, x1.hash, x1.num_transactions
588            );
589        }
590        r
591    });
592    let poh_duration_us = now.elapsed().as_micros() as u64;
593    EntryVerificationState {
594        verification_status: res,
595        poh_duration_us,
596    }
597}
598
599pub fn verify_entries_cpu(
600    entries: &[EntryVerificationData],
601    start_hash: &Hash,
602) -> EntryVerificationState {
603    verify_entries_cpu_generic(entries, start_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) -> EntryVerificationState;
610    fn verify_cpu_generic(&self, start_hash: &Hash) -> EntryVerificationState;
611    fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState;
612}
613
614impl EntrySlice for [Entry] {
615    fn verify(&self, start_hash: &Hash, thread_pool: &ThreadPool) -> EntryVerificationState {
616        let verification_entries = entries_to_verification_data(self);
617        verify_entries_cpu_in_pool(&verification_entries, start_hash, thread_pool)
618    }
619
620    fn verify_cpu_generic(&self, start_hash: &Hash) -> EntryVerificationState {
621        let verification_entries = entries_to_verification_data(self);
622        verify_entries_cpu_generic(&verification_entries, start_hash)
623    }
624
625    fn verify_cpu(&self, start_hash: &Hash) -> EntryVerificationState {
626        let verification_entries = entries_to_verification_data(self);
627        verify_entries_cpu(&verification_entries, start_hash)
628    }
629}
630
631impl EntrySliceTickCheck for [Entry] {
632    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
633        // When hashes_per_tick is 0, hashing is disabled.
634        if hashes_per_tick == 0 {
635            return true;
636        }
637
638        for entry in self {
639            *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
640            if entry.is_tick() {
641                if entry.num_hashes == 0 {
642                    return false;
643                }
644                if *tick_hash_count != hashes_per_tick {
645                    warn!(
646                        "invalid tick hash count!: entry: {entry:#?}, tick_hash_count: \
647                         {tick_hash_count}, hashes_per_tick: {hashes_per_tick}"
648                    );
649                    return false;
650                }
651                *tick_hash_count = 0;
652            }
653        }
654        *tick_hash_count < hashes_per_tick
655    }
656
657    fn tick_count(&self) -> u64 {
658        self.iter().filter(|e| e.is_tick()).count() as u64
659    }
660}
661pub trait EntrySliceTickCheck {
662    /// Checks that each entry tick has the correct number of hashes. Entry slices do not
663    /// necessarily end in a tick, so `tick_hash_count` is used to carry over the hash count
664    /// for the next entry slice.
665    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
666
667    /// Counts tick entries
668    fn tick_count(&self) -> u64;
669}
670
671impl<D: TransactionData> EntrySliceTickCheck for [EntryView<D>] {
672    fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool {
673        // When hashes_per_tick is 0, hashing is disabled.
674        if hashes_per_tick == 0 {
675            return true;
676        }
677
678        for entry in self {
679            *tick_hash_count = tick_hash_count.saturating_add(entry.num_hashes);
680            if entry.is_tick() {
681                if entry.num_hashes == 0 {
682                    return false;
683                }
684                if *tick_hash_count != hashes_per_tick {
685                    warn!(
686                        "invalid tick hash count!: entry: {entry:#?}, tick_hash_count: \
687                         {tick_hash_count}, hashes_per_tick: {hashes_per_tick}"
688                    );
689                    return false;
690                }
691                *tick_hash_count = 0;
692            }
693        }
694        *tick_hash_count < hashes_per_tick
695    }
696
697    fn tick_count(&self) -> u64 {
698        self.iter().filter(|e| e.is_tick()).count() as u64
699    }
700}
701
702pub fn next_entry_mut(start: &mut Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
703    let entry = Entry::new(start, num_hashes, transactions);
704    *start = entry.hash;
705    entry
706}
707
708pub fn create_ticks(num_ticks: u64, hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
709    repeat_with(|| next_entry_mut(&mut hash, hashes_per_tick, vec![]))
710        .take(num_ticks as usize)
711        .collect()
712}
713
714/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
715pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
716    let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
717    next_versioned_entry(prev_hash, num_hashes, transactions)
718}
719
720/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
721pub fn next_versioned_entry(
722    prev_hash: &Hash,
723    num_hashes: u64,
724    transactions: Vec<VersionedTransaction>,
725) -> Entry {
726    assert!(num_hashes > 0 || transactions.is_empty());
727    Entry {
728        num_hashes,
729        hash: next_hash(prev_hash, num_hashes, &transactions),
730        transactions,
731    }
732}
733
734pub fn thread_pool_for_tests() -> ThreadPool {
735    // Allocate fewer threads for unit tests
736    // Unit tests typically aren't creating massive blocks to verify, and
737    // multiple tests could be running in parallel so any further parallelism
738    // will do more harm than good
739    rayon::ThreadPoolBuilder::new()
740        .num_threads(4)
741        .thread_name(|i| format!("solEntryTest{i:02}"))
742        .build()
743        .expect("new rayon threadpool")
744}
745
746#[cfg(feature = "dev-context-only-utils")]
747pub fn thread_pool_for_benches() -> ThreadPool {
748    rayon::ThreadPoolBuilder::new()
749        .num_threads(num_cpus::get())
750        .thread_name(|i| format!("solEntryBnch{i:02}"))
751        .build()
752        .expect("new rayon threadpool")
753}
754
755#[cfg(test)]
756mod tests {
757    use {
758        super::*,
759        crate::block_component::{BlockComponent, ParsedBlockComponent},
760        agave_reserved_account_keys::ReservedAccountKeys,
761        agave_transaction_view::transaction_view::SanitizedTransactionView,
762        bytes::Bytes,
763        rand::{Rng, rng},
764        rayon::ThreadPoolBuilder,
765        solana_hash::Hash,
766        solana_keypair::Keypair,
767        solana_measure::measure::Measure,
768        solana_message::{
769            MessageHeader, VersionedMessage, compiled_instruction::CompiledInstruction, v1,
770        },
771        solana_perf::test_tx::test_tx,
772        solana_pubkey::Pubkey,
773        solana_runtime_transaction::{
774            runtime_transaction::{ReplayTransaction, RuntimeTransaction},
775            sanitize_config::sanitize_config,
776        },
777        solana_sha256_hasher::hash,
778        solana_signature::Signature,
779        solana_signer::Signer,
780        solana_system_transaction as system_transaction,
781        solana_transaction::{sanitized::MessageHash, versioned::VersionedTransaction},
782        solana_transaction_error::TransactionResult as Result,
783    };
784
785    fn entry_view_from_entry(entry: Entry) -> EntryView<Bytes> {
786        let component = BlockComponent::new_entry_batch(vec![entry]).unwrap();
787        let bytes = wincode::serialize(&component).unwrap();
788        let ParsedBlockComponent::EntryBatch(mut views) =
789            crate::block_component_parser::parse(bytes).unwrap()
790        else {
791            panic!("expected EntryBatch");
792        };
793        views.pop().unwrap()
794    }
795
796    fn simple_v1_transaction_for_deserialization_tests() -> VersionedTransaction {
797        VersionedTransaction {
798            signatures: vec![Signature::default()],
799            message: VersionedMessage::V1(v1::Message {
800                header: MessageHeader {
801                    num_required_signatures: 1,
802                    num_readonly_signed_accounts: 0,
803                    num_readonly_unsigned_accounts: 1,
804                },
805                config: v1::TransactionConfig::empty(),
806                lifetime_specifier: Hash::new_unique(),
807                account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
808                instructions: vec![CompiledInstruction {
809                    program_id_index: 1,
810                    accounts: vec![0],
811                    data: vec![],
812                }],
813            }),
814        }
815    }
816
817    fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: Hash) -> Vec<Entry> {
818        repeat_with(|| {
819            let hashes_per_tick = rng().random_range(1..max_hashes_per_tick);
820            next_entry_mut(&mut hash, hashes_per_tick, vec![])
821        })
822        .take(num_ticks as usize)
823        .collect()
824    }
825
826    #[test]
827    fn test_entry_verify() {
828        let zero = Hash::default();
829        let one = hash(zero.as_ref());
830        assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case, never used
831        assert!(!Entry::new_tick(0, &zero).verify(&one)); // base case, bad
832        assert!(next_entry(&zero, 1, vec![]).verify(&zero)); // inductive step
833        assert!(!next_entry(&zero, 1, vec![]).verify(&one)); // inductive step, bad
834    }
835
836    fn test_verify_transactions<Tx: TransactionWithMeta + Send + Sync + 'static>(
837        entries: Vec<EntryView<Bytes>>,
838        skip_verification: bool,
839        thread_pool: &ThreadPool,
840        verify: impl Fn(UnsanitizedTransactionView<Bytes>) -> Result<Tx> + Send + Sync,
841    ) -> bool {
842        let num_txs = entries.iter().map(|entry| entry.transactions.len()).sum();
843        let txs = validate_and_hash_transactions(entries, num_txs, thread_pool, verify);
844        let Ok(txs) = txs else {
845            return false;
846        };
847        skip_verification || txs.unverified_signatures.verify().is_ok()
848    }
849
850    #[test]
851    fn test_entry_transaction_verify() {
852        let zero = Hash::default();
853
854        // First, verify entries
855        let keypair = Keypair::new();
856        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
857        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
858        let e0 = Entry::new(&zero, 0, vec![tx0, tx1]);
859        assert!(e0.verify(&zero));
860        let tx2 = system_transaction::transfer(&keypair, &keypair.pubkey(), 2, zero);
861        let tx3 = system_transaction::transfer(&keypair, &keypair.pubkey(), 3, zero);
862        let e1 = Entry::new(&zero, 0, vec![tx2, tx3]);
863        assert!(e1.verify(&zero));
864
865        let es = vec![entry_view_from_entry(e0), entry_view_from_entry(e1)];
866        let thread_pool = ThreadPoolBuilder::new().build().unwrap();
867
868        // Next, verify entry slice
869        let verify_transaction = {
870            move |unsanitized: UnsanitizedTransactionView<Bytes>| -> Result<ReplayTransaction> {
871                let sanitized = unsanitized
872                    .sanitize(&sanitize_config())
873                    .map_err(|_| TransactionError::SanitizeFailure)?;
874                let statically_loaded =
875                    RuntimeTransaction::<SanitizedTransactionView<Bytes>>::try_new(
876                        sanitized,
877                        MessageHash::Compute,
878                        None,
879                    )?;
880                ReplayTransaction::try_new(
881                    statically_loaded,
882                    None,
883                    &ReservedAccountKeys::empty_key_set(),
884                )
885            }
886        };
887
888        assert!(test_verify_transactions(
889            es,
890            false,
891            &thread_pool,
892            verify_transaction
893        ));
894    }
895
896    #[test]
897    fn test_validate_and_hash_transactions_and_signatures() {
898        let thread_pool = ThreadPoolBuilder::new().build().unwrap();
899        let zero = Hash::default();
900        let keypair = Keypair::new();
901        let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
902        let entries = vec![entry_view_from_entry(Entry::new(&zero, 0, vec![tx]))];
903
904        let validate_and_hash_transaction =
905            move |unsanitized: UnsanitizedTransactionView<Bytes>| -> Result<ReplayTransaction> {
906                let sanitized = unsanitized
907                    .sanitize(&sanitize_config())
908                    .map_err(|_| TransactionError::SanitizeFailure)?;
909                let statically_loaded =
910                    RuntimeTransaction::<SanitizedTransactionView<Bytes>>::try_new(
911                        sanitized,
912                        MessageHash::Compute,
913                        None,
914                    )?;
915                ReplayTransaction::try_new(
916                    statically_loaded,
917                    None,
918                    &ReservedAccountKeys::empty_key_set(),
919                )
920            };
921        let txs =
922            validate_and_hash_transactions(entries, 1, &thread_pool, validate_and_hash_transaction)
923                .expect("transaction validation and hashing must not verify signatures");
924        assert_eq!(txs.entries.len(), 1);
925        assert!(txs.unverified_signatures.verify().is_ok());
926
927        let mut tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
928        tx.signatures[0] = solana_signature::Signature::default();
929        let entries = vec![entry_view_from_entry(Entry::new(&zero, 0, vec![tx]))];
930        let txs =
931            validate_and_hash_transactions(entries, 1, &thread_pool, validate_and_hash_transaction)
932                .expect("transaction validation and hashing must not verify signatures");
933        assert_eq!(txs.entries.len(), 1);
934        assert!(matches!(
935            txs.unverified_signatures.verify(),
936            Err(solana_transaction_error::TransactionError::SignatureFailure)
937        ));
938    }
939
940    #[test]
941    fn test_transaction_reorder_attack() {
942        let zero = Hash::default();
943
944        // First, verify entries
945        let keypair = Keypair::new();
946        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
947        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
948        let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]);
949        assert!(e0.verify(&zero));
950
951        // Next, swap two transactions and ensure verification fails.
952        e0.transactions[0] = tx1.into(); // <-- attack
953        e0.transactions[1] = tx0.into();
954        assert!(!e0.verify(&zero));
955    }
956
957    #[test]
958    fn test_transaction_signing() {
959        let thread_pool = thread_pool_for_tests();
960
961        use solana_signature::Signature;
962        let zero = Hash::default();
963
964        let keypair = Keypair::new();
965        let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
966        let tx1 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, zero);
967
968        // Verify entry with 2 transactions
969        let mut e0 = [Entry::new(&zero, 0, vec![tx0, tx1])];
970        assert!(e0.verify(&zero, &thread_pool).status());
971
972        // Clear signature of the first transaction, see that it does not verify
973        let orig_sig = e0[0].transactions[0].signatures[0];
974        e0[0].transactions[0].signatures[0] = Signature::default();
975        assert!(!e0.verify(&zero, &thread_pool).status());
976
977        // restore original signature
978        e0[0].transactions[0].signatures[0] = orig_sig;
979        assert!(e0.verify(&zero, &thread_pool).status());
980
981        // Resize signatures and see verification fails.
982        let len = e0[0].transactions[0].signatures.len();
983        e0[0].transactions[0]
984            .signatures
985            .resize(len - 1, Signature::default());
986        assert!(!e0.verify(&zero, &thread_pool).status());
987
988        // Pass an entry with no transactions
989        let e0 = [Entry::new(&zero, 0, vec![])];
990        assert!(e0.verify(&zero, &thread_pool).status());
991    }
992
993    #[test]
994    fn test_next_entry() {
995        let zero = Hash::default();
996        let tick = next_entry(&zero, 1, vec![]);
997        assert_eq!(tick.num_hashes, 1);
998        assert_ne!(tick.hash, zero);
999
1000        let tick = next_entry(&zero, 0, vec![]);
1001        assert_eq!(tick.num_hashes, 0);
1002        assert_eq!(tick.hash, zero);
1003
1004        let keypair = Keypair::new();
1005        let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero);
1006        let entry0 = next_entry(&zero, 1, vec![tx0.clone()]);
1007        assert_eq!(entry0.num_hashes, 1);
1008        assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0.into()]));
1009    }
1010
1011    #[test]
1012    #[should_panic]
1013    fn test_next_entry_panic() {
1014        let zero = Hash::default();
1015        let keypair = Keypair::new();
1016        let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 0, zero);
1017        next_entry(&zero, 0, vec![tx]);
1018    }
1019
1020    #[test]
1021    fn test_verify_slice1() {
1022        agave_logger::setup();
1023        let thread_pool = thread_pool_for_tests();
1024
1025        let zero = Hash::default();
1026        let one = hash(zero.as_ref());
1027        // base case
1028        assert!(vec![][..].verify(&zero, &thread_pool).status());
1029        // singleton case 1
1030        assert!(
1031            vec![Entry::new_tick(0, &zero)][..]
1032                .verify(&zero, &thread_pool)
1033                .status()
1034        );
1035        // singleton case 2, bad
1036        assert!(
1037            !vec![Entry::new_tick(0, &zero)][..]
1038                .verify(&one, &thread_pool)
1039                .status()
1040        );
1041        // inductive step
1042        assert!(
1043            vec![next_entry(&zero, 0, vec![]); 2][..]
1044                .verify(&zero, &thread_pool)
1045                .status()
1046        );
1047
1048        let mut bad_ticks = vec![next_entry(&zero, 0, vec![]); 2];
1049        bad_ticks[1].hash = one;
1050        // inductive step, bad
1051        assert!(!bad_ticks.verify(&zero, &thread_pool).status());
1052    }
1053
1054    #[test]
1055    fn test_verify_slice_with_hashes1() {
1056        agave_logger::setup();
1057        let thread_pool = thread_pool_for_tests();
1058
1059        let zero = Hash::default();
1060        let one = hash(zero.as_ref());
1061        let two = hash(one.as_ref());
1062        // base case
1063        assert!(vec![][..].verify(&one, &thread_pool).status());
1064        // singleton case 1
1065        assert!(
1066            vec![Entry::new_tick(1, &two)][..]
1067                .verify(&one, &thread_pool)
1068                .status()
1069        );
1070        // singleton case 2, bad
1071        assert!(
1072            !vec![Entry::new_tick(1, &two)][..]
1073                .verify(&two, &thread_pool)
1074                .status()
1075        );
1076
1077        let mut ticks = vec![next_entry(&one, 1, vec![])];
1078        ticks.push(next_entry(&ticks.last().unwrap().hash, 1, vec![]));
1079        // inductive step
1080        assert!(ticks.verify(&one, &thread_pool).status());
1081
1082        let mut bad_ticks = vec![next_entry(&one, 1, vec![])];
1083        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![]));
1084        bad_ticks[1].hash = one;
1085        // inductive step, bad
1086        assert!(!bad_ticks.verify(&one, &thread_pool).status());
1087    }
1088
1089    #[test]
1090    fn test_verify_slice_with_hashes_and_transactions() {
1091        agave_logger::setup();
1092        let thread_pool = thread_pool_for_tests();
1093
1094        let zero = Hash::default();
1095        let one = hash(zero.as_ref());
1096        let two = hash(one.as_ref());
1097        let alice_keypair = Keypair::new();
1098        let bob_keypair = Keypair::new();
1099        let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one);
1100        let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one);
1101        // base case
1102        assert!(vec![][..].verify(&one, &thread_pool).status());
1103        // singleton case 1
1104        assert!(
1105            vec![next_entry(&one, 1, vec![tx0.clone()])][..]
1106                .verify(&one, &thread_pool)
1107                .status()
1108        );
1109        // singleton case 2, bad
1110        assert!(
1111            !vec![next_entry(&one, 1, vec![tx0.clone()])][..]
1112                .verify(&two, &thread_pool)
1113                .status()
1114        );
1115
1116        let mut ticks = vec![next_entry(&one, 1, vec![tx0.clone()])];
1117        ticks.push(next_entry(
1118            &ticks.last().unwrap().hash,
1119            1,
1120            vec![tx1.clone()],
1121        ));
1122
1123        // inductive step
1124        assert!(ticks.verify(&one, &thread_pool).status());
1125
1126        let mut bad_ticks = vec![next_entry(&one, 1, vec![tx0])];
1127        bad_ticks.push(next_entry(&bad_ticks.last().unwrap().hash, 1, vec![tx1]));
1128        bad_ticks[1].hash = one;
1129        // inductive step, bad
1130        assert!(!bad_ticks.verify(&one, &thread_pool).status());
1131    }
1132
1133    #[test]
1134    fn test_verify_tick_hash_count() {
1135        let hashes_per_tick = 10;
1136        let tx = VersionedTransaction::default();
1137
1138        let no_hash_tx_entry = Entry {
1139            transactions: vec![tx.clone()],
1140            ..Entry::default()
1141        };
1142        let single_hash_tx_entry = Entry {
1143            transactions: vec![tx.clone()],
1144            num_hashes: 1,
1145            ..Entry::default()
1146        };
1147        let partial_tx_entry = Entry {
1148            num_hashes: hashes_per_tick - 1,
1149            transactions: vec![tx.clone()],
1150            ..Entry::default()
1151        };
1152        let full_tx_entry = Entry {
1153            num_hashes: hashes_per_tick,
1154            transactions: vec![tx.clone()],
1155            ..Entry::default()
1156        };
1157        let max_hash_tx_entry = Entry {
1158            transactions: vec![tx],
1159            num_hashes: u64::MAX,
1160            ..Entry::default()
1161        };
1162
1163        let no_hash_tick_entry = Entry::new_tick(0, &Hash::default());
1164        let single_hash_tick_entry = Entry::new_tick(1, &Hash::default());
1165        let partial_tick_entry = Entry::new_tick(hashes_per_tick - 1, &Hash::default());
1166        let full_tick_entry = Entry::new_tick(hashes_per_tick, &Hash::default());
1167        let max_hash_tick_entry = Entry::new_tick(u64::MAX, &Hash::default());
1168
1169        // empty batch should succeed if hashes_per_tick hasn't been reached
1170        let mut tick_hash_count = 0;
1171        let mut entries: Vec<Entry> = vec![];
1172        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1173        assert_eq!(tick_hash_count, 0);
1174
1175        // empty batch should fail if hashes_per_tick has been reached
1176        tick_hash_count = hashes_per_tick;
1177        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1178        assert_eq!(tick_hash_count, hashes_per_tick);
1179        tick_hash_count = 0;
1180
1181        // validation is disabled when hashes_per_tick == 0
1182        entries = vec![max_hash_tx_entry.clone()];
1183        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, 0));
1184        assert_eq!(tick_hash_count, 0);
1185
1186        // partial tick should fail
1187        entries = vec![partial_tick_entry.clone()];
1188        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1189        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1190        tick_hash_count = 0;
1191
1192        // full tick entry should succeed
1193        entries = vec![no_hash_tx_entry, full_tick_entry.clone()];
1194        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1195        assert_eq!(tick_hash_count, 0);
1196
1197        // oversized tick entry should fail
1198        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick - 1));
1199        assert_eq!(tick_hash_count, hashes_per_tick);
1200        tick_hash_count = 0;
1201
1202        // partial tx entry without tick entry should succeed
1203        entries = vec![partial_tx_entry];
1204        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1205        assert_eq!(tick_hash_count, hashes_per_tick - 1);
1206        tick_hash_count = 0;
1207
1208        // no hash tick entry should fail
1209        entries = vec![no_hash_tick_entry.clone()];
1210        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1211        assert_eq!(tick_hash_count, 0);
1212
1213        // full tx entry with no hash tick entry should still fail
1214        entries = vec![full_tx_entry.clone(), no_hash_tick_entry];
1215        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1216        assert_eq!(tick_hash_count, hashes_per_tick);
1217        tick_hash_count = 0;
1218
1219        // full tx entry with oversized tick entry should fail
1220        entries = vec![full_tx_entry.clone(), single_hash_tick_entry.clone()];
1221        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1222        assert_eq!(tick_hash_count, hashes_per_tick + 1);
1223        tick_hash_count = 0;
1224
1225        // full tx entry without tick entry should fail
1226        entries = vec![full_tx_entry];
1227        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1228        assert_eq!(tick_hash_count, hashes_per_tick);
1229        tick_hash_count = 0;
1230
1231        // tx entry and a tick should succeed
1232        entries = vec![single_hash_tx_entry.clone(), partial_tick_entry];
1233        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1234        assert_eq!(tick_hash_count, 0);
1235
1236        // many tx entries and a tick should succeed
1237        let tx_entries: Vec<Entry> = (0..hashes_per_tick - 1)
1238            .map(|_| single_hash_tx_entry.clone())
1239            .collect();
1240        entries = [tx_entries, vec![single_hash_tick_entry]].concat();
1241        assert!(entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1242        assert_eq!(tick_hash_count, 0);
1243
1244        // check overflow saturation should fail
1245        entries = vec![full_tick_entry.clone(), max_hash_tick_entry];
1246        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1247        assert_eq!(tick_hash_count, u64::MAX);
1248        tick_hash_count = 0;
1249
1250        // check overflow saturation should fail
1251        entries = vec![max_hash_tx_entry, full_tick_entry];
1252        assert!(!entries.verify_tick_hash_count(&mut tick_hash_count, hashes_per_tick));
1253        assert_eq!(tick_hash_count, u64::MAX);
1254    }
1255
1256    #[test]
1257    fn test_poh_verify_fuzz() {
1258        agave_logger::setup();
1259        for _ in 0..100 {
1260            let mut time = Measure::start("ticks");
1261            let num_ticks = rng().random_range(1..100);
1262            info!("create {num_ticks} ticks:");
1263            let mut entries = create_random_ticks(num_ticks, 100, Hash::default());
1264            time.stop();
1265
1266            let mut modified = false;
1267            if rng().random_ratio(1, 2) {
1268                modified = true;
1269                let modify_idx = rng().random_range(0..num_ticks) as usize;
1270                entries[modify_idx].hash = hash(&[1, 2, 3]);
1271            }
1272
1273            info!("done.. {time}");
1274            let mut time = Measure::start("poh");
1275            let res = entries
1276                .verify(&Hash::default(), &thread_pool_for_tests())
1277                .status();
1278            assert_eq!(res, !modified);
1279            time.stop();
1280            info!("{time} {res}");
1281        }
1282    }
1283
1284    #[test]
1285    fn test_hash_transactions() {
1286        let mut transactions: Vec<_> = [test_tx(), test_tx(), test_tx()]
1287            .into_iter()
1288            .map(VersionedTransaction::from)
1289            .collect();
1290
1291        // Test different permutations of the transactions have different final hashes.
1292        // i.e. that **order** of transactions is included in the hash.
1293        let hash1 = hash_transactions(&transactions);
1294        transactions.swap(0, 1);
1295        let hash2 = hash_transactions(&transactions);
1296        assert_ne!(hash1, hash2);
1297    }
1298
1299    #[test]
1300    fn test_deserialize_entries_rejects_txv1_unknown_config_mask_bit() {
1301        let tx = simple_v1_transaction_for_deserialization_tests();
1302        let entries = vec![next_versioned_entry(&Hash::default(), 1, vec![tx.clone()])];
1303        let mut serialized_entries = wincode::serialize(&entries).unwrap();
1304
1305        assert!(wincode::deserialize::<Vec<Entry>>(&serialized_entries).is_ok());
1306
1307        let serialized_tx = wincode::serialize(&tx).unwrap();
1308        let tx_offset = serialized_entries
1309            .windows(serialized_tx.len())
1310            .position(|window| window == serialized_tx)
1311            .expect("serialized transaction should be embedded in serialized entry");
1312
1313        // txv1 begins with the version byte and the 3-byte legacy header.
1314        const TXV1_CONFIG_MASK_OFFSET: usize = 1 + 3;
1315        let mask_offset = tx_offset + TXV1_CONFIG_MASK_OFFSET;
1316        let mask_range = mask_offset..mask_offset + core::mem::size_of::<u32>();
1317        let mask = u32::from_le_bytes(serialized_entries[mask_range.clone()].try_into().unwrap());
1318        assert_eq!(mask, 0);
1319
1320        let unknown_config_mask_bit = 1u32
1321            .checked_shl(v1::TransactionConfigMask::KNOWN_BITS.trailing_ones())
1322            .expect("txv1 config mask should have at least one unknown bit");
1323        assert_ne!(
1324            unknown_config_mask_bit & v1::TransactionConfigMask::KNOWN_BITS,
1325            unknown_config_mask_bit
1326        );
1327        serialized_entries[mask_range]
1328            .copy_from_slice(&(mask | unknown_config_mask_bit).to_le_bytes());
1329
1330        assert!(matches!(
1331            wincode::deserialize::<Vec<Entry>>(&serialized_entries),
1332            Err(wincode::ReadError::InvalidValue(
1333                "invalid transaction config mask"
1334            ))
1335        ));
1336    }
1337}