solana_runtime/
prioritization_fee_cache.rs

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