solana_runtime/
prioritization_fee_cache.rs

1use {
2    crate::{bank::Bank, prioritization_fee::PrioritizationFee},
3    crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError},
4    log::*,
5    solana_accounts_db::account_locks::validate_account_locks,
6    solana_clock::{BankId, Slot},
7    solana_measure::measure_us,
8    solana_pubkey::Pubkey,
9    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
10    std::{
11        collections::{BTreeMap, HashMap},
12        sync::{
13            atomic::{AtomicU64, Ordering},
14            Arc, RwLock,
15        },
16        thread::{sleep, Builder, JoinHandle},
17        time::Duration,
18    },
19};
20
21/// The maximum number of blocks to keep in `PrioritizationFeeCache`, ie.
22/// the amount of history generally desired to estimate the prioritization fee needed to
23/// land a transaction in the current block.
24const MAX_NUM_RECENT_BLOCKS: u64 = 150;
25
26/// Thers is no guarantee that slots coming in order, we keep extra slots in the buffer.
27const MAX_UNFINALIZED_SLOTS: u64 = 128;
28
29type UnfinalizedPrioritizationFees = BTreeMap<Slot, HashMap<BankId, PrioritizationFee>>;
30
31#[derive(Debug, Default)]
32struct PrioritizationFeeCacheMetrics {
33    // Count of transactions that successfully updated each slot's prioritization fee cache.
34    successful_transaction_update_count: AtomicU64,
35
36    // Count of duplicated banks being purged
37    purged_duplicated_bank_count: AtomicU64,
38
39    // Accumulated time spent on tracking prioritization fee for each slot.
40    total_update_elapsed_us: AtomicU64,
41
42    // Accumulated time spent on acquiring cache write lock.
43    total_cache_lock_elapsed_us: AtomicU64,
44
45    // Accumulated time spent on updating block prioritization fees.
46    total_entry_update_elapsed_us: AtomicU64,
47
48    // Accumulated time spent on finalizing block prioritization fees.
49    total_block_finalize_elapsed_us: AtomicU64,
50
51    // Accumulated time spent on calculate transaction fees.
52    total_calculate_prioritization_fee_elapsed_us: AtomicU64,
53}
54
55impl PrioritizationFeeCacheMetrics {
56    fn accumulate_successful_transaction_update_count(&self, val: u64) {
57        self.successful_transaction_update_count
58            .fetch_add(val, Ordering::Relaxed);
59    }
60
61    fn accumulate_total_purged_duplicated_bank_count(&self, val: u64) {
62        self.purged_duplicated_bank_count
63            .fetch_add(val, Ordering::Relaxed);
64    }
65
66    fn accumulate_total_update_elapsed_us(&self, val: u64) {
67        self.total_update_elapsed_us
68            .fetch_add(val, Ordering::Relaxed);
69    }
70
71    fn accumulate_total_cache_lock_elapsed_us(&self, val: u64) {
72        self.total_cache_lock_elapsed_us
73            .fetch_add(val, Ordering::Relaxed);
74    }
75
76    fn accumulate_total_entry_update_elapsed_us(&self, val: u64) {
77        self.total_entry_update_elapsed_us
78            .fetch_add(val, Ordering::Relaxed);
79    }
80
81    fn accumulate_total_block_finalize_elapsed_us(&self, val: u64) {
82        self.total_block_finalize_elapsed_us
83            .fetch_add(val, Ordering::Relaxed);
84    }
85
86    fn accumulate_total_calculate_prioritization_fee_elapsed_us(&self, val: u64) {
87        self.total_calculate_prioritization_fee_elapsed_us
88            .fetch_add(val, Ordering::Relaxed);
89    }
90
91    fn report(&self, slot: Slot) {
92        datapoint_info!(
93            "block_prioritization_fee_counters",
94            ("slot", slot as i64, i64),
95            (
96                "successful_transaction_update_count",
97                self.successful_transaction_update_count
98                    .swap(0, Ordering::Relaxed) as i64,
99                i64
100            ),
101            (
102                "purged_duplicated_bank_count",
103                self.purged_duplicated_bank_count.swap(0, Ordering::Relaxed) as i64,
104                i64
105            ),
106            (
107                "total_update_elapsed_us",
108                self.total_update_elapsed_us.swap(0, Ordering::Relaxed) as i64,
109                i64
110            ),
111            (
112                "total_cache_lock_elapsed_us",
113                self.total_cache_lock_elapsed_us.swap(0, Ordering::Relaxed) as i64,
114                i64
115            ),
116            (
117                "total_entry_update_elapsed_us",
118                self.total_entry_update_elapsed_us
119                    .swap(0, Ordering::Relaxed) as i64,
120                i64
121            ),
122            (
123                "total_block_finalize_elapsed_us",
124                self.total_block_finalize_elapsed_us
125                    .swap(0, Ordering::Relaxed) as i64,
126                i64
127            ),
128            (
129                "total_calculate_prioritization_fee_elapsed_us",
130                self.total_calculate_prioritization_fee_elapsed_us
131                    .swap(0, Ordering::Relaxed) as i64,
132                i64
133            ),
134        );
135    }
136}
137
138#[derive(Debug)]
139enum CacheServiceUpdate {
140    TransactionUpdate {
141        slot: Slot,
142        bank_id: BankId,
143        compute_unit_price: u64,
144        prioritization_fee: u64,
145        writable_accounts: Vec<Pubkey>,
146    },
147    BankFinalized {
148        slot: Slot,
149        bank_id: BankId,
150    },
151    Exit,
152}
153
154/// Stores up to MAX_NUM_RECENT_BLOCKS recent block's prioritization fee,
155/// A separate internal thread `service_thread` handles additional tasks when a bank is frozen,
156/// and collecting stats and reporting metrics.
157#[derive(Debug)]
158pub struct PrioritizationFeeCache {
159    cache: Arc<RwLock<BTreeMap<Slot, PrioritizationFee>>>,
160    service_thread: Option<JoinHandle<()>>,
161    sender: Sender<CacheServiceUpdate>,
162    metrics: Arc<PrioritizationFeeCacheMetrics>,
163}
164
165impl Default for PrioritizationFeeCache {
166    fn default() -> Self {
167        Self::new(MAX_NUM_RECENT_BLOCKS)
168    }
169}
170
171impl Drop for PrioritizationFeeCache {
172    fn drop(&mut self) {
173        let _ = self.sender.send(CacheServiceUpdate::Exit);
174        self.service_thread
175            .take()
176            .unwrap()
177            .join()
178            .expect("Prioritization fee cache servicing thread failed to join");
179    }
180}
181
182impl PrioritizationFeeCache {
183    pub fn new(capacity: u64) -> Self {
184        let cache = Arc::new(RwLock::new(BTreeMap::new()));
185        let (sender, receiver) = unbounded();
186        let metrics = Arc::new(PrioritizationFeeCacheMetrics::default());
187
188        let service_thread = Some(
189            Builder::new()
190                .name("solPrFeeCachSvc".to_string())
191                .spawn({
192                    let cache = cache.clone();
193                    let metrics = metrics.clone();
194                    move || Self::service_loop(cache, capacity as usize, receiver, metrics)
195                })
196                .unwrap(),
197        );
198
199        PrioritizationFeeCache {
200            cache,
201            service_thread,
202            sender,
203            metrics,
204        }
205    }
206
207    /// Update with a list of non-vote transactions' compute_budget_details and account_locks; Only
208    /// transactions have both valid compute_budget_details and account_locks will be used to update
209    /// fee_cache asynchronously.
210    pub fn update<'a, Tx: TransactionWithMeta + 'a>(
211        &self,
212        bank: &Bank,
213        txs: impl Iterator<Item = &'a Tx>,
214    ) {
215        let (_, send_updates_us) = measure_us!({
216            for sanitized_transaction in txs {
217                // Vote transactions are not prioritized, therefore they are excluded from
218                // updating fee_cache.
219                if sanitized_transaction.is_simple_vote_transaction() {
220                    continue;
221                }
222
223                let compute_budget_limits = sanitized_transaction
224                    .compute_budget_instruction_details()
225                    .sanitize_and_convert_to_compute_budget_limits(&bank.feature_set);
226
227                let lock_result = validate_account_locks(
228                    sanitized_transaction.account_keys(),
229                    bank.get_transaction_account_lock_limit(),
230                );
231
232                if compute_budget_limits.is_err() || lock_result.is_err() {
233                    continue;
234                }
235                let compute_budget_limits = compute_budget_limits.unwrap();
236
237                // filter out any transaction that requests zero compute_unit_limit
238                // since its priority fee amount is not instructive
239                if compute_budget_limits.compute_unit_limit == 0 {
240                    continue;
241                }
242
243                let writable_accounts = sanitized_transaction
244                    .account_keys()
245                    .iter()
246                    .enumerate()
247                    .filter(|(index, _)| sanitized_transaction.is_writable(*index))
248                    .map(|(_, key)| *key)
249                    .collect();
250
251                let (prioritization_fee, calculate_prioritization_fee_us) = measure_us!({
252                    solana_fee_structure::FeeBudgetLimits::from(compute_budget_limits)
253                        .prioritization_fee
254                });
255                self.metrics
256                    .accumulate_total_calculate_prioritization_fee_elapsed_us(
257                        calculate_prioritization_fee_us,
258                    );
259
260                self.sender
261                    .send(CacheServiceUpdate::TransactionUpdate {
262                        slot: bank.slot(),
263                        bank_id: bank.bank_id(),
264                        compute_unit_price: compute_budget_limits.compute_unit_price,
265                        prioritization_fee,
266                        writable_accounts,
267                    })
268                    .unwrap_or_else(|err| {
269                        warn!("prioritization fee cache transaction updates failed: {err:?}");
270                    });
271            }
272        });
273
274        self.metrics
275            .accumulate_total_update_elapsed_us(send_updates_us);
276    }
277
278    /// Finalize prioritization fee when it's bank is completely replayed from blockstore,
279    /// by pruning irrelevant accounts to save space, and marking its availability for queries.
280    pub fn finalize_priority_fee(&self, slot: Slot, bank_id: BankId) {
281        self.sender
282            .send(CacheServiceUpdate::BankFinalized { slot, bank_id })
283            .unwrap_or_else(|err| {
284                warn!("prioritization fee cache signalling bank frozen failed: {err:?}")
285            });
286    }
287
288    /// Internal function is invoked by worker thread to update slot's minimum prioritization fee.
289    fn update_cache(
290        unfinalized: &mut UnfinalizedPrioritizationFees,
291        slot: Slot,
292        bank_id: BankId,
293        compute_unit_price: u64,
294        prioritization_fee: u64,
295        writable_accounts: Vec<Pubkey>,
296        metrics: &PrioritizationFeeCacheMetrics,
297    ) {
298        let (_, entry_update_us) = measure_us!(unfinalized
299            .entry(slot)
300            .or_default()
301            .entry(bank_id)
302            .or_default()
303            .update(compute_unit_price, prioritization_fee, writable_accounts));
304        metrics.accumulate_total_entry_update_elapsed_us(entry_update_us);
305        metrics.accumulate_successful_transaction_update_count(1);
306    }
307
308    fn finalize_slot(
309        unfinalized: &mut UnfinalizedPrioritizationFees,
310        cache: &RwLock<BTreeMap<Slot, PrioritizationFee>>,
311        cache_max_size: usize,
312        slot: Slot,
313        bank_id: BankId,
314        metrics: &PrioritizationFeeCacheMetrics,
315    ) {
316        if unfinalized.is_empty() {
317            return;
318        }
319
320        // prune cache by evicting write account entry from prioritization fee if its fee is less
321        // or equal to block's minimum transaction fee, because they are irrelevant in calculating
322        // block minimum fee.
323        let (slot_prioritization_fee, slot_finalize_us) = measure_us!({
324            // remove unfinalized slots
325            *unfinalized =
326                unfinalized.split_off(&slot.checked_sub(MAX_UNFINALIZED_SLOTS).unwrap_or_default());
327
328            let Some(mut slot_prioritization_fee) = unfinalized.remove(&slot) else {
329                return;
330            };
331
332            // Only retain priority fee reported from optimistically confirmed bank
333            let pre_purge_bank_count = slot_prioritization_fee.len() as u64;
334            let mut prioritization_fee = slot_prioritization_fee.remove(&bank_id);
335            let post_purge_bank_count = prioritization_fee.as_ref().map(|_| 1).unwrap_or(0);
336            metrics.accumulate_total_purged_duplicated_bank_count(
337                pre_purge_bank_count.saturating_sub(post_purge_bank_count),
338            );
339            // It should be rare that optimistically confirmed bank had no prioritized
340            // transactions, but duplicated and unconfirmed bank had.
341            if pre_purge_bank_count > 0 && post_purge_bank_count == 0 {
342                warn!(
343                    "Finalized bank has empty prioritization fee cache. slot {slot} bank id \
344                     {bank_id}"
345                );
346            }
347
348            if let Some(prioritization_fee) = &mut prioritization_fee {
349                if let Err(err) = prioritization_fee.mark_block_completed() {
350                    error!("Unsuccessful finalizing slot {slot}, bank ID {bank_id}: {err:?}");
351                }
352                prioritization_fee.report_metrics(slot);
353            }
354            prioritization_fee
355        });
356        metrics.accumulate_total_block_finalize_elapsed_us(slot_finalize_us);
357
358        // Create new cache entry
359        if let Some(slot_prioritization_fee) = slot_prioritization_fee {
360            let (_, cache_lock_us) = measure_us!({
361                let mut cache = cache.write().unwrap();
362                while cache.len() >= cache_max_size {
363                    cache.pop_first();
364                }
365                cache.insert(slot, slot_prioritization_fee);
366            });
367            metrics.accumulate_total_cache_lock_elapsed_us(cache_lock_us);
368        }
369    }
370
371    fn service_loop(
372        cache: Arc<RwLock<BTreeMap<Slot, PrioritizationFee>>>,
373        cache_max_size: usize,
374        receiver: Receiver<CacheServiceUpdate>,
375        metrics: Arc<PrioritizationFeeCacheMetrics>,
376    ) {
377        // Potentially there are more than one bank that updates Prioritization Fee
378        // for a slot. The updates are tracked and finalized by bank_id.
379        let mut unfinalized = UnfinalizedPrioritizationFees::new();
380
381        loop {
382            let update = match receiver.try_recv() {
383                Ok(update) => update,
384                Err(TryRecvError::Empty) => {
385                    sleep(Duration::from_millis(5));
386                    continue;
387                }
388                Err(err @ TryRecvError::Disconnected) => {
389                    info!("PrioritizationFeeCache::service_loop() is stopping because: {err}");
390                    break;
391                }
392            };
393            match update {
394                CacheServiceUpdate::TransactionUpdate {
395                    slot,
396                    bank_id,
397                    compute_unit_price,
398                    prioritization_fee,
399                    writable_accounts,
400                } => Self::update_cache(
401                    &mut unfinalized,
402                    slot,
403                    bank_id,
404                    compute_unit_price,
405                    prioritization_fee,
406                    writable_accounts,
407                    &metrics,
408                ),
409                CacheServiceUpdate::BankFinalized { slot, bank_id } => {
410                    Self::finalize_slot(
411                        &mut unfinalized,
412                        &cache,
413                        cache_max_size,
414                        slot,
415                        bank_id,
416                        &metrics,
417                    );
418                    metrics.report(slot);
419                }
420                CacheServiceUpdate::Exit => {
421                    break;
422                }
423            }
424        }
425    }
426
427    /// Returns number of blocks that have finalized minimum fees collection
428    pub fn available_block_count(&self) -> usize {
429        self.cache.read().unwrap().len()
430    }
431
432    pub fn get_prioritization_fees(&self, account_keys: &[Pubkey]) -> Vec<(Slot, u64)> {
433        self.cache
434            .read()
435            .unwrap()
436            .iter()
437            .map(|(slot, slot_prioritization_fee)| {
438                let mut fee = slot_prioritization_fee
439                    .get_min_compute_unit_price()
440                    .unwrap_or_default();
441                for account_key in account_keys {
442                    if let Some(account_fee) =
443                        slot_prioritization_fee.get_writable_account_fee(account_key)
444                    {
445                        fee = std::cmp::max(fee, account_fee);
446                    }
447                }
448                (*slot, fee)
449            })
450            .collect()
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use {
457        super::*,
458        crate::{
459            bank::Bank,
460            bank_forks::BankForks,
461            genesis_utils::{create_genesis_config, GenesisConfigInfo},
462        },
463        solana_compute_budget_interface::ComputeBudgetInstruction,
464        solana_message::Message,
465        solana_pubkey::Pubkey,
466        solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
467        solana_system_interface::instruction as system_instruction,
468        solana_transaction::{sanitized::SanitizedTransaction, Transaction},
469    };
470
471    fn build_sanitized_transaction_for_test(
472        compute_unit_price: u64,
473        signer_account: &Pubkey,
474        write_account: &Pubkey,
475    ) -> RuntimeTransaction<SanitizedTransaction> {
476        let transaction = Transaction::new_unsigned(Message::new(
477            &[
478                system_instruction::transfer(signer_account, write_account, 1),
479                ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price),
480            ],
481            Some(signer_account),
482        ));
483
484        RuntimeTransaction::from_transaction_for_tests(transaction)
485    }
486
487    // update fee cache is asynchronous, this test helper blocks until update is completed.
488    fn sync_update<'a>(
489        prioritization_fee_cache: &PrioritizationFeeCache,
490        bank: Arc<Bank>,
491        txs: impl ExactSizeIterator<Item = &'a RuntimeTransaction<SanitizedTransaction>>,
492    ) {
493        let expected_update_count = prioritization_fee_cache
494            .metrics
495            .successful_transaction_update_count
496            .load(Ordering::Relaxed)
497            .saturating_add(txs.len() as u64);
498
499        prioritization_fee_cache.update(&bank, txs);
500
501        // wait till expected number of transaction updates have occurred...
502        while prioritization_fee_cache
503            .metrics
504            .successful_transaction_update_count
505            .load(Ordering::Relaxed)
506            != expected_update_count
507        {
508            std::thread::sleep(std::time::Duration::from_millis(10));
509        }
510    }
511
512    // finalization is asynchronous, this test helper blocks until finalization is completed.
513    fn sync_finalize_priority_fee_for_test(
514        prioritization_fee_cache: &PrioritizationFeeCache,
515        slot: Slot,
516        bank_id: BankId,
517    ) {
518        // mark as finalized
519        prioritization_fee_cache.finalize_priority_fee(slot, bank_id);
520
521        // wait till finalization is done
522        loop {
523            let cache = prioritization_fee_cache.cache.read().unwrap();
524            if let Some(slot_cache) = cache.get(&slot) {
525                if slot_cache.is_finalized() {
526                    return;
527                }
528            }
529            drop(cache);
530
531            std::thread::sleep(std::time::Duration::from_millis(10));
532        }
533    }
534
535    #[test]
536    fn test_prioritization_fee_cache_update() {
537        solana_logger::setup();
538        let write_account_a = Pubkey::new_unique();
539        let write_account_b = Pubkey::new_unique();
540        let write_account_c = Pubkey::new_unique();
541
542        // Set up test with 3 transactions, in format of [fee, write-accounts...],
543        // Shall expect fee cache is updated in following sequence:
544        // transaction                    block minimum prioritization fee cache
545        // [fee, write_accounts...]  -->  [block, account_a, account_b, account_c]
546        // -----------------------------------------------------------------------
547        // [5,   a, b             ]  -->  [5,     5,         5,         nil      ]
548        // [9,      b, c          ]  -->  [5,     5,         5,         9        ]
549        // [2,   a,    c          ]  -->  [2,     2,         5,         2        ]
550        //
551        let txs = vec![
552            build_sanitized_transaction_for_test(5, &write_account_a, &write_account_b),
553            build_sanitized_transaction_for_test(9, &write_account_b, &write_account_c),
554            build_sanitized_transaction_for_test(2, &write_account_a, &write_account_c),
555        ];
556
557        let bank = Arc::new(Bank::default_for_tests());
558        let slot = bank.slot();
559
560        let prioritization_fee_cache = PrioritizationFeeCache::default();
561        sync_update(&prioritization_fee_cache, bank.clone(), txs.iter());
562
563        // assert empty cache
564        {
565            let lock = prioritization_fee_cache.cache.read().unwrap();
566            assert!(lock.get(&slot).is_none());
567        }
568
569        // assert after prune, account a and c should be removed from cache to save space
570        {
571            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, slot, bank.bank_id());
572            let lock = prioritization_fee_cache.cache.read().unwrap();
573            let fee = lock.get(&slot).unwrap();
574            assert_eq!(2, fee.get_min_compute_unit_price().unwrap());
575            assert!(fee.get_writable_account_fee(&write_account_a).is_none());
576            assert_eq!(5, fee.get_writable_account_fee(&write_account_b).unwrap());
577            assert!(fee.get_writable_account_fee(&write_account_c).is_none());
578        }
579    }
580
581    #[test]
582    fn test_available_block_count() {
583        let prioritization_fee_cache = PrioritizationFeeCache::default();
584
585        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
586        let bank0 = Bank::new_for_benches(&genesis_config);
587        let bank_forks = BankForks::new_rw_arc(bank0);
588        let bank = bank_forks.read().unwrap().working_bank();
589        let collector = solana_pubkey::new_rand();
590
591        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 1));
592        sync_update(
593            &prioritization_fee_cache,
594            bank1.clone(),
595            vec![build_sanitized_transaction_for_test(
596                1,
597                &Pubkey::new_unique(),
598                &Pubkey::new_unique(),
599            )]
600            .iter(),
601        );
602        sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 1, bank1.bank_id());
603
604        // add slot 2 entry to cache, but not finalize it
605        let bank2 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 2));
606        let txs = vec![build_sanitized_transaction_for_test(
607            1,
608            &Pubkey::new_unique(),
609            &Pubkey::new_unique(),
610        )];
611        sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
612
613        let bank3 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 3));
614        sync_update(
615            &prioritization_fee_cache,
616            bank3.clone(),
617            vec![build_sanitized_transaction_for_test(
618                1,
619                &Pubkey::new_unique(),
620                &Pubkey::new_unique(),
621            )]
622            .iter(),
623        );
624        sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 3, bank3.bank_id());
625
626        // assert available block count should be 2 finalized blocks
627        assert_eq!(2, prioritization_fee_cache.available_block_count());
628    }
629
630    #[test]
631    fn test_get_prioritization_fees() {
632        solana_logger::setup();
633        let write_account_a = Pubkey::new_unique();
634        let write_account_b = Pubkey::new_unique();
635        let write_account_c = Pubkey::new_unique();
636
637        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
638        let bank0 = Bank::new_for_benches(&genesis_config);
639        let bank_forks = BankForks::new_rw_arc(bank0);
640        let bank = bank_forks.read().unwrap().working_bank();
641        let collector = solana_pubkey::new_rand();
642        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 1));
643        let bank2 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 2));
644        let bank3 = Arc::new(Bank::new_from_parent(bank, &collector, 3));
645
646        let prioritization_fee_cache = PrioritizationFeeCache::default();
647
648        // Assert no minimum fee from empty cache
649        assert!(prioritization_fee_cache
650            .get_prioritization_fees(&[])
651            .is_empty());
652        assert!(prioritization_fee_cache
653            .get_prioritization_fees(&[write_account_a])
654            .is_empty());
655        assert!(prioritization_fee_cache
656            .get_prioritization_fees(&[write_account_b])
657            .is_empty());
658        assert!(prioritization_fee_cache
659            .get_prioritization_fees(&[write_account_c])
660            .is_empty());
661        assert!(prioritization_fee_cache
662            .get_prioritization_fees(&[write_account_a, write_account_b])
663            .is_empty());
664        assert!(prioritization_fee_cache
665            .get_prioritization_fees(&[write_account_a, write_account_b, write_account_c])
666            .is_empty());
667
668        // Assert after add one transaction for slot 1
669        {
670            let txs = vec![
671                build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b),
672                build_sanitized_transaction_for_test(
673                    1,
674                    &Pubkey::new_unique(),
675                    &Pubkey::new_unique(),
676                ),
677            ];
678            sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter());
679            // before block is marked as completed
680            assert!(prioritization_fee_cache
681                .get_prioritization_fees(&[])
682                .is_empty());
683            assert!(prioritization_fee_cache
684                .get_prioritization_fees(&[write_account_a])
685                .is_empty());
686            assert!(prioritization_fee_cache
687                .get_prioritization_fees(&[write_account_b])
688                .is_empty());
689            assert!(prioritization_fee_cache
690                .get_prioritization_fees(&[write_account_c])
691                .is_empty());
692            assert!(prioritization_fee_cache
693                .get_prioritization_fees(&[write_account_a, write_account_b])
694                .is_empty());
695            assert!(prioritization_fee_cache
696                .get_prioritization_fees(&[write_account_a, write_account_b, write_account_c])
697                .is_empty());
698            // after block is completed
699            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 1, bank1.bank_id());
700            assert_eq!(
701                vec![(1, 1)],
702                prioritization_fee_cache.get_prioritization_fees(&[])
703            );
704            assert_eq!(
705                vec![(1, 2)],
706                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
707            );
708            assert_eq!(
709                vec![(1, 2)],
710                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
711            );
712            assert_eq!(
713                vec![(1, 1)],
714                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
715            );
716            assert_eq!(
717                vec![(1, 2)],
718                prioritization_fee_cache
719                    .get_prioritization_fees(&[write_account_a, write_account_b])
720            );
721            assert_eq!(
722                vec![(1, 2)],
723                prioritization_fee_cache.get_prioritization_fees(&[
724                    write_account_a,
725                    write_account_b,
726                    write_account_c
727                ])
728            );
729        }
730
731        // Assert after add one transaction for slot 2
732        {
733            let txs = vec![
734                build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c),
735                build_sanitized_transaction_for_test(
736                    3,
737                    &Pubkey::new_unique(),
738                    &Pubkey::new_unique(),
739                ),
740            ];
741            sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
742            // before block is marked as completed
743            assert_eq!(
744                vec![(1, 1)],
745                prioritization_fee_cache.get_prioritization_fees(&[])
746            );
747            assert_eq!(
748                vec![(1, 2)],
749                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
750            );
751            assert_eq!(
752                vec![(1, 2)],
753                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
754            );
755            assert_eq!(
756                vec![(1, 1)],
757                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
758            );
759            assert_eq!(
760                vec![(1, 2)],
761                prioritization_fee_cache
762                    .get_prioritization_fees(&[write_account_a, write_account_b])
763            );
764            assert_eq!(
765                vec![(1, 2)],
766                prioritization_fee_cache.get_prioritization_fees(&[
767                    write_account_a,
768                    write_account_b,
769                    write_account_c
770                ])
771            );
772            // after block is completed
773            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 2, bank2.bank_id());
774            assert_eq!(
775                vec![(1, 1), (2, 3)],
776                prioritization_fee_cache.get_prioritization_fees(&[]),
777            );
778            assert_eq!(
779                vec![(1, 2), (2, 3)],
780                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
781            );
782            assert_eq!(
783                vec![(1, 2), (2, 4)],
784                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
785            );
786            assert_eq!(
787                vec![(1, 1), (2, 4)],
788                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
789            );
790            assert_eq!(
791                vec![(1, 2), (2, 4)],
792                prioritization_fee_cache
793                    .get_prioritization_fees(&[write_account_a, write_account_b]),
794            );
795            assert_eq!(
796                vec![(1, 2), (2, 4)],
797                prioritization_fee_cache.get_prioritization_fees(&[
798                    write_account_a,
799                    write_account_b,
800                    write_account_c,
801                ]),
802            );
803        }
804
805        // Assert after add one transaction for slot 3
806        {
807            let txs = vec![
808                build_sanitized_transaction_for_test(6, &write_account_a, &write_account_c),
809                build_sanitized_transaction_for_test(
810                    5,
811                    &Pubkey::new_unique(),
812                    &Pubkey::new_unique(),
813                ),
814            ];
815            sync_update(&prioritization_fee_cache, bank3.clone(), txs.iter());
816            // before block is marked as completed
817            assert_eq!(
818                vec![(1, 1), (2, 3)],
819                prioritization_fee_cache.get_prioritization_fees(&[]),
820            );
821            assert_eq!(
822                vec![(1, 2), (2, 3)],
823                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
824            );
825            assert_eq!(
826                vec![(1, 2), (2, 4)],
827                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
828            );
829            assert_eq!(
830                vec![(1, 1), (2, 4)],
831                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
832            );
833            assert_eq!(
834                vec![(1, 2), (2, 4)],
835                prioritization_fee_cache
836                    .get_prioritization_fees(&[write_account_a, write_account_b]),
837            );
838            assert_eq!(
839                vec![(1, 2), (2, 4)],
840                prioritization_fee_cache.get_prioritization_fees(&[
841                    write_account_a,
842                    write_account_b,
843                    write_account_c,
844                ]),
845            );
846            // after block is completed
847            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 3, bank3.bank_id());
848            assert_eq!(
849                vec![(1, 1), (2, 3), (3, 5)],
850                prioritization_fee_cache.get_prioritization_fees(&[]),
851            );
852            assert_eq!(
853                vec![(1, 2), (2, 3), (3, 6)],
854                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
855            );
856            assert_eq!(
857                vec![(1, 2), (2, 4), (3, 5)],
858                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
859            );
860            assert_eq!(
861                vec![(1, 1), (2, 4), (3, 6)],
862                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
863            );
864            assert_eq!(
865                vec![(1, 2), (2, 4), (3, 6)],
866                prioritization_fee_cache
867                    .get_prioritization_fees(&[write_account_a, write_account_b]),
868            );
869            assert_eq!(
870                vec![(1, 2), (2, 4), (3, 6)],
871                prioritization_fee_cache.get_prioritization_fees(&[
872                    write_account_a,
873                    write_account_b,
874                    write_account_c,
875                ]),
876            );
877        }
878    }
879
880    #[test]
881    fn test_purge_duplicated_bank() {
882        // duplicated bank can exists for same slot before OC.
883        // prioritization_fee_cache should only have data from OC-ed bank
884        solana_logger::setup();
885        let write_account_a = Pubkey::new_unique();
886        let write_account_b = Pubkey::new_unique();
887        let write_account_c = Pubkey::new_unique();
888
889        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
890        let bank0 = Bank::new_for_benches(&genesis_config);
891        let bank_forks = BankForks::new_rw_arc(bank0);
892        let bank = bank_forks.read().unwrap().working_bank();
893        let collector = solana_pubkey::new_rand();
894        let slot: Slot = 999;
895        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, slot));
896        let bank2 = Arc::new(Bank::new_from_parent(bank, &collector, slot + 1));
897
898        let prioritization_fee_cache = PrioritizationFeeCache::default();
899
900        // Assert after add transactions for bank1 of slot 1
901        {
902            let txs = vec![
903                build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b),
904                build_sanitized_transaction_for_test(
905                    1,
906                    &Pubkey::new_unique(),
907                    &Pubkey::new_unique(),
908                ),
909            ];
910            sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter());
911        }
912
913        // Assert after add transactions for bank2 of slot 1
914        {
915            let txs = vec![
916                build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c),
917                build_sanitized_transaction_for_test(
918                    3,
919                    &Pubkey::new_unique(),
920                    &Pubkey::new_unique(),
921                ),
922            ];
923            sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
924        }
925
926        // Assert after finalize with bank1 of slot 1,
927        {
928            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, slot, bank1.bank_id());
929
930            // and data available for query are from bank1
931            assert_eq!(
932                vec![(slot, 1)],
933                prioritization_fee_cache.get_prioritization_fees(&[])
934            );
935            assert_eq!(
936                vec![(slot, 2)],
937                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
938            );
939            assert_eq!(
940                vec![(slot, 2)],
941                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
942            );
943            assert_eq!(
944                vec![(slot, 1)],
945                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
946            );
947            assert_eq!(
948                vec![(slot, 2)],
949                prioritization_fee_cache
950                    .get_prioritization_fees(&[write_account_a, write_account_b])
951            );
952            assert_eq!(
953                vec![(slot, 2)],
954                prioritization_fee_cache.get_prioritization_fees(&[
955                    write_account_a,
956                    write_account_b,
957                    write_account_c
958                ])
959            );
960        }
961    }
962}