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::instructions_processor::process_compute_budget_instructions,
8    solana_sdk::{
9        clock::{BankId, Slot},
10        pubkey::Pubkey,
11        transaction::SanitizedTransaction,
12    },
13    solana_svm_transaction::svm_message::SVMMessage,
14    std::{
15        collections::{BTreeMap, HashMap},
16        sync::{
17            atomic::{AtomicU64, Ordering},
18            Arc, RwLock,
19        },
20        thread::{sleep, Builder, JoinHandle},
21        time::Duration,
22    },
23};
24
25/// The maximum number of blocks to keep in `PrioritizationFeeCache`, ie.
26/// the amount of history generally desired to estimate the prioritization fee needed to
27/// land a transaction in the current block.
28const MAX_NUM_RECENT_BLOCKS: u64 = 150;
29
30/// Thers is no guarantee that slots coming in order, we keep extra slots in the buffer.
31const MAX_UNFINALIZED_SLOTS: u64 = 128;
32
33type UnfinalizedPrioritizationFees = BTreeMap<Slot, HashMap<BankId, PrioritizationFee>>;
34
35#[derive(Debug, Default)]
36struct PrioritizationFeeCacheMetrics {
37    // Count of transactions that successfully updated each slot's prioritization fee cache.
38    successful_transaction_update_count: AtomicU64,
39
40    // Count of duplicated banks being purged
41    purged_duplicated_bank_count: AtomicU64,
42
43    // Accumulated time spent on tracking prioritization fee for each slot.
44    total_update_elapsed_us: AtomicU64,
45
46    // Accumulated time spent on acquiring cache write lock.
47    total_cache_lock_elapsed_us: AtomicU64,
48
49    // Accumulated time spent on updating block prioritization fees.
50    total_entry_update_elapsed_us: AtomicU64,
51
52    // Accumulated time spent on finalizing block prioritization fees.
53    total_block_finalize_elapsed_us: AtomicU64,
54}
55
56impl PrioritizationFeeCacheMetrics {
57    fn accumulate_successful_transaction_update_count(&self, val: u64) {
58        self.successful_transaction_update_count
59            .fetch_add(val, Ordering::Relaxed);
60    }
61
62    fn accumulate_total_purged_duplicated_bank_count(&self, val: u64) {
63        self.purged_duplicated_bank_count
64            .fetch_add(val, Ordering::Relaxed);
65    }
66
67    fn accumulate_total_update_elapsed_us(&self, val: u64) {
68        self.total_update_elapsed_us
69            .fetch_add(val, Ordering::Relaxed);
70    }
71
72    fn accumulate_total_cache_lock_elapsed_us(&self, val: u64) {
73        self.total_cache_lock_elapsed_us
74            .fetch_add(val, Ordering::Relaxed);
75    }
76
77    fn accumulate_total_entry_update_elapsed_us(&self, val: u64) {
78        self.total_entry_update_elapsed_us
79            .fetch_add(val, Ordering::Relaxed);
80    }
81
82    fn accumulate_total_block_finalize_elapsed_us(&self, val: u64) {
83        self.total_block_finalize_elapsed_us
84            .fetch_add(val, Ordering::Relaxed);
85    }
86
87    fn report(&self, slot: Slot) {
88        datapoint_info!(
89            "block_prioritization_fee_counters",
90            ("slot", slot as i64, i64),
91            (
92                "successful_transaction_update_count",
93                self.successful_transaction_update_count
94                    .swap(0, Ordering::Relaxed) as i64,
95                i64
96            ),
97            (
98                "purged_duplicated_bank_count",
99                self.purged_duplicated_bank_count.swap(0, Ordering::Relaxed) as i64,
100                i64
101            ),
102            (
103                "total_update_elapsed_us",
104                self.total_update_elapsed_us.swap(0, Ordering::Relaxed) as i64,
105                i64
106            ),
107            (
108                "total_cache_lock_elapsed_us",
109                self.total_cache_lock_elapsed_us.swap(0, Ordering::Relaxed) as i64,
110                i64
111            ),
112            (
113                "total_entry_update_elapsed_us",
114                self.total_entry_update_elapsed_us
115                    .swap(0, Ordering::Relaxed) as i64,
116                i64
117            ),
118            (
119                "total_block_finalize_elapsed_us",
120                self.total_block_finalize_elapsed_us
121                    .swap(0, Ordering::Relaxed) as i64,
122                i64
123            ),
124        );
125    }
126}
127
128#[derive(Debug)]
129enum CacheServiceUpdate {
130    TransactionUpdate {
131        slot: Slot,
132        bank_id: BankId,
133        transaction_fee: u64,
134        writable_accounts: Vec<Pubkey>,
135    },
136    BankFinalized {
137        slot: Slot,
138        bank_id: BankId,
139    },
140    Exit,
141}
142
143/// Stores up to MAX_NUM_RECENT_BLOCKS recent block's prioritization fee,
144/// A separate internal thread `service_thread` handles additional tasks when a bank is frozen,
145/// and collecting stats and reporting metrics.
146#[derive(Debug)]
147pub struct PrioritizationFeeCache {
148    cache: Arc<RwLock<BTreeMap<Slot, PrioritizationFee>>>,
149    service_thread: Option<JoinHandle<()>>,
150    sender: Sender<CacheServiceUpdate>,
151    metrics: Arc<PrioritizationFeeCacheMetrics>,
152}
153
154impl Default for PrioritizationFeeCache {
155    fn default() -> Self {
156        Self::new(MAX_NUM_RECENT_BLOCKS)
157    }
158}
159
160impl Drop for PrioritizationFeeCache {
161    fn drop(&mut self) {
162        let _ = self.sender.send(CacheServiceUpdate::Exit);
163        self.service_thread
164            .take()
165            .unwrap()
166            .join()
167            .expect("Prioritization fee cache servicing thread failed to join");
168    }
169}
170
171impl PrioritizationFeeCache {
172    pub fn new(capacity: u64) -> Self {
173        let cache = Arc::new(RwLock::new(BTreeMap::new()));
174        let (sender, receiver) = unbounded();
175        let metrics = Arc::new(PrioritizationFeeCacheMetrics::default());
176
177        let service_thread = Some(
178            Builder::new()
179                .name("solPrFeeCachSvc".to_string())
180                .spawn({
181                    let cache = cache.clone();
182                    let metrics = metrics.clone();
183                    move || Self::service_loop(cache, capacity as usize, receiver, metrics)
184                })
185                .unwrap(),
186        );
187
188        PrioritizationFeeCache {
189            cache,
190            service_thread,
191            sender,
192            metrics,
193        }
194    }
195
196    /// Update with a list of non-vote transactions' compute_budget_details and account_locks; Only
197    /// transactions have both valid compute_budget_details and account_locks will be used to update
198    /// fee_cache asynchronously.
199    pub fn update<'a>(&self, bank: &Bank, txs: impl Iterator<Item = &'a SanitizedTransaction>) {
200        let (_, send_updates_us) = measure_us!({
201            for sanitized_transaction in txs {
202                // Vote transactions are not prioritized, therefore they are excluded from
203                // updating fee_cache.
204                if sanitized_transaction.is_simple_vote_transaction() {
205                    continue;
206                }
207
208                let compute_budget_limits = process_compute_budget_instructions(
209                    SVMMessage::program_instructions_iter(sanitized_transaction),
210                    &bank.feature_set,
211                );
212
213                let message = sanitized_transaction.message();
214                let lock_result = validate_account_locks(
215                    message.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 = message
231                    .account_keys()
232                    .iter()
233                    .enumerate()
234                    .filter(|(index, _)| message.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_sdk::{
444            compute_budget::ComputeBudgetInstruction,
445            message::Message,
446            pubkey::Pubkey,
447            system_instruction,
448            transaction::{SanitizedTransaction, Transaction},
449        },
450    };
451
452    fn build_sanitized_transaction_for_test(
453        compute_unit_price: u64,
454        signer_account: &Pubkey,
455        write_account: &Pubkey,
456    ) -> SanitizedTransaction {
457        let transaction = Transaction::new_unsigned(Message::new(
458            &[
459                system_instruction::transfer(signer_account, write_account, 1),
460                ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price),
461            ],
462            Some(signer_account),
463        ));
464
465        SanitizedTransaction::from_transaction_for_tests(transaction)
466    }
467
468    // update fee cache is asynchronous, this test helper blocks until update is completed.
469    fn sync_update<'a>(
470        prioritization_fee_cache: &PrioritizationFeeCache,
471        bank: Arc<Bank>,
472        txs: impl ExactSizeIterator<Item = &'a SanitizedTransaction>,
473    ) {
474        let expected_update_count = prioritization_fee_cache
475            .metrics
476            .successful_transaction_update_count
477            .load(Ordering::Relaxed)
478            .saturating_add(txs.len() as u64);
479
480        prioritization_fee_cache.update(&bank, txs);
481
482        // wait till expected number of transaction updates have occurred...
483        while prioritization_fee_cache
484            .metrics
485            .successful_transaction_update_count
486            .load(Ordering::Relaxed)
487            != expected_update_count
488        {
489            std::thread::sleep(std::time::Duration::from_millis(10));
490        }
491    }
492
493    // finalization is asynchronous, this test helper blocks until finalization is completed.
494    fn sync_finalize_priority_fee_for_test(
495        prioritization_fee_cache: &PrioritizationFeeCache,
496        slot: Slot,
497        bank_id: BankId,
498    ) {
499        // mark as finalized
500        prioritization_fee_cache.finalize_priority_fee(slot, bank_id);
501
502        // wait till finalization is done
503        loop {
504            let cache = prioritization_fee_cache.cache.read().unwrap();
505            if let Some(slot_cache) = cache.get(&slot) {
506                if slot_cache.is_finalized() {
507                    return;
508                }
509            }
510            drop(cache);
511
512            std::thread::sleep(std::time::Duration::from_millis(10));
513        }
514    }
515
516    #[test]
517    fn test_prioritization_fee_cache_update() {
518        solana_logger::setup();
519        let write_account_a = Pubkey::new_unique();
520        let write_account_b = Pubkey::new_unique();
521        let write_account_c = Pubkey::new_unique();
522
523        // Set up test with 3 transactions, in format of [fee, write-accounts...],
524        // Shall expect fee cache is updated in following sequence:
525        // transaction                    block minimum prioritization fee cache
526        // [fee, write_accounts...]  -->  [block, account_a, account_b, account_c]
527        // -----------------------------------------------------------------------
528        // [5,   a, b             ]  -->  [5,     5,         5,         nil      ]
529        // [9,      b, c          ]  -->  [5,     5,         5,         9        ]
530        // [2,   a,    c          ]  -->  [2,     2,         5,         2        ]
531        //
532        let txs = vec![
533            build_sanitized_transaction_for_test(5, &write_account_a, &write_account_b),
534            build_sanitized_transaction_for_test(9, &write_account_b, &write_account_c),
535            build_sanitized_transaction_for_test(2, &write_account_a, &write_account_c),
536        ];
537
538        let bank = Arc::new(Bank::default_for_tests());
539        let slot = bank.slot();
540
541        let prioritization_fee_cache = PrioritizationFeeCache::default();
542        sync_update(&prioritization_fee_cache, bank.clone(), txs.iter());
543
544        // assert empty cache
545        {
546            let lock = prioritization_fee_cache.cache.read().unwrap();
547            assert!(lock.get(&slot).is_none());
548        }
549
550        // assert after prune, account a and c should be removed from cache to save space
551        {
552            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, slot, bank.bank_id());
553            let lock = prioritization_fee_cache.cache.read().unwrap();
554            let fee = lock.get(&slot).unwrap();
555            assert_eq!(2, fee.get_min_transaction_fee().unwrap());
556            assert!(fee.get_writable_account_fee(&write_account_a).is_none());
557            assert_eq!(5, fee.get_writable_account_fee(&write_account_b).unwrap());
558            assert!(fee.get_writable_account_fee(&write_account_c).is_none());
559        }
560    }
561
562    #[test]
563    fn test_available_block_count() {
564        let prioritization_fee_cache = PrioritizationFeeCache::default();
565
566        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
567        let bank0 = Bank::new_for_benches(&genesis_config);
568        let bank_forks = BankForks::new_rw_arc(bank0);
569        let bank = bank_forks.read().unwrap().working_bank();
570        let collector = solana_sdk::pubkey::new_rand();
571
572        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 1));
573        sync_update(
574            &prioritization_fee_cache,
575            bank1.clone(),
576            vec![build_sanitized_transaction_for_test(
577                1,
578                &Pubkey::new_unique(),
579                &Pubkey::new_unique(),
580            )]
581            .iter(),
582        );
583        sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 1, bank1.bank_id());
584
585        // add slot 2 entry to cache, but not finalize it
586        let bank2 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 2));
587        let txs = vec![build_sanitized_transaction_for_test(
588            1,
589            &Pubkey::new_unique(),
590            &Pubkey::new_unique(),
591        )];
592        sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
593
594        let bank3 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 3));
595        sync_update(
596            &prioritization_fee_cache,
597            bank3.clone(),
598            vec![build_sanitized_transaction_for_test(
599                1,
600                &Pubkey::new_unique(),
601                &Pubkey::new_unique(),
602            )]
603            .iter(),
604        );
605        sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 3, bank3.bank_id());
606
607        // assert available block count should be 2 finalized blocks
608        assert_eq!(2, prioritization_fee_cache.available_block_count());
609    }
610
611    #[test]
612    fn test_get_prioritization_fees() {
613        solana_logger::setup();
614        let write_account_a = Pubkey::new_unique();
615        let write_account_b = Pubkey::new_unique();
616        let write_account_c = Pubkey::new_unique();
617
618        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
619        let bank0 = Bank::new_for_benches(&genesis_config);
620        let bank_forks = BankForks::new_rw_arc(bank0);
621        let bank = bank_forks.read().unwrap().working_bank();
622        let collector = solana_sdk::pubkey::new_rand();
623        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 1));
624        let bank2 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 2));
625        let bank3 = Arc::new(Bank::new_from_parent(bank, &collector, 3));
626
627        let prioritization_fee_cache = PrioritizationFeeCache::default();
628
629        // Assert no minimum fee from empty cache
630        assert!(prioritization_fee_cache
631            .get_prioritization_fees(&[])
632            .is_empty());
633        assert!(prioritization_fee_cache
634            .get_prioritization_fees(&[write_account_a])
635            .is_empty());
636        assert!(prioritization_fee_cache
637            .get_prioritization_fees(&[write_account_b])
638            .is_empty());
639        assert!(prioritization_fee_cache
640            .get_prioritization_fees(&[write_account_c])
641            .is_empty());
642        assert!(prioritization_fee_cache
643            .get_prioritization_fees(&[write_account_a, write_account_b])
644            .is_empty());
645        assert!(prioritization_fee_cache
646            .get_prioritization_fees(&[write_account_a, write_account_b, write_account_c])
647            .is_empty());
648
649        // Assert after add one transaction for slot 1
650        {
651            let txs = vec![
652                build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b),
653                build_sanitized_transaction_for_test(
654                    1,
655                    &Pubkey::new_unique(),
656                    &Pubkey::new_unique(),
657                ),
658            ];
659            sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter());
660            // before block is marked as completed
661            assert!(prioritization_fee_cache
662                .get_prioritization_fees(&[])
663                .is_empty());
664            assert!(prioritization_fee_cache
665                .get_prioritization_fees(&[write_account_a])
666                .is_empty());
667            assert!(prioritization_fee_cache
668                .get_prioritization_fees(&[write_account_b])
669                .is_empty());
670            assert!(prioritization_fee_cache
671                .get_prioritization_fees(&[write_account_c])
672                .is_empty());
673            assert!(prioritization_fee_cache
674                .get_prioritization_fees(&[write_account_a, write_account_b])
675                .is_empty());
676            assert!(prioritization_fee_cache
677                .get_prioritization_fees(&[write_account_a, write_account_b, write_account_c])
678                .is_empty());
679            // after block is completed
680            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 1, bank1.bank_id());
681            assert_eq!(
682                vec![(1, 1)],
683                prioritization_fee_cache.get_prioritization_fees(&[])
684            );
685            assert_eq!(
686                vec![(1, 2)],
687                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
688            );
689            assert_eq!(
690                vec![(1, 2)],
691                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
692            );
693            assert_eq!(
694                vec![(1, 1)],
695                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
696            );
697            assert_eq!(
698                vec![(1, 2)],
699                prioritization_fee_cache
700                    .get_prioritization_fees(&[write_account_a, write_account_b])
701            );
702            assert_eq!(
703                vec![(1, 2)],
704                prioritization_fee_cache.get_prioritization_fees(&[
705                    write_account_a,
706                    write_account_b,
707                    write_account_c
708                ])
709            );
710        }
711
712        // Assert after add one transaction for slot 2
713        {
714            let txs = vec![
715                build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c),
716                build_sanitized_transaction_for_test(
717                    3,
718                    &Pubkey::new_unique(),
719                    &Pubkey::new_unique(),
720                ),
721            ];
722            sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
723            // before block is marked as completed
724            assert_eq!(
725                vec![(1, 1)],
726                prioritization_fee_cache.get_prioritization_fees(&[])
727            );
728            assert_eq!(
729                vec![(1, 2)],
730                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
731            );
732            assert_eq!(
733                vec![(1, 2)],
734                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
735            );
736            assert_eq!(
737                vec![(1, 1)],
738                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
739            );
740            assert_eq!(
741                vec![(1, 2)],
742                prioritization_fee_cache
743                    .get_prioritization_fees(&[write_account_a, write_account_b])
744            );
745            assert_eq!(
746                vec![(1, 2)],
747                prioritization_fee_cache.get_prioritization_fees(&[
748                    write_account_a,
749                    write_account_b,
750                    write_account_c
751                ])
752            );
753            // after block is completed
754            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 2, bank2.bank_id());
755            assert_eq!(
756                vec![(1, 1), (2, 3)],
757                prioritization_fee_cache.get_prioritization_fees(&[]),
758            );
759            assert_eq!(
760                vec![(1, 2), (2, 3)],
761                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
762            );
763            assert_eq!(
764                vec![(1, 2), (2, 4)],
765                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
766            );
767            assert_eq!(
768                vec![(1, 1), (2, 4)],
769                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
770            );
771            assert_eq!(
772                vec![(1, 2), (2, 4)],
773                prioritization_fee_cache
774                    .get_prioritization_fees(&[write_account_a, write_account_b]),
775            );
776            assert_eq!(
777                vec![(1, 2), (2, 4)],
778                prioritization_fee_cache.get_prioritization_fees(&[
779                    write_account_a,
780                    write_account_b,
781                    write_account_c,
782                ]),
783            );
784        }
785
786        // Assert after add one transaction for slot 3
787        {
788            let txs = vec![
789                build_sanitized_transaction_for_test(6, &write_account_a, &write_account_c),
790                build_sanitized_transaction_for_test(
791                    5,
792                    &Pubkey::new_unique(),
793                    &Pubkey::new_unique(),
794                ),
795            ];
796            sync_update(&prioritization_fee_cache, bank3.clone(), txs.iter());
797            // before block is marked as completed
798            assert_eq!(
799                vec![(1, 1), (2, 3)],
800                prioritization_fee_cache.get_prioritization_fees(&[]),
801            );
802            assert_eq!(
803                vec![(1, 2), (2, 3)],
804                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
805            );
806            assert_eq!(
807                vec![(1, 2), (2, 4)],
808                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
809            );
810            assert_eq!(
811                vec![(1, 1), (2, 4)],
812                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
813            );
814            assert_eq!(
815                vec![(1, 2), (2, 4)],
816                prioritization_fee_cache
817                    .get_prioritization_fees(&[write_account_a, write_account_b]),
818            );
819            assert_eq!(
820                vec![(1, 2), (2, 4)],
821                prioritization_fee_cache.get_prioritization_fees(&[
822                    write_account_a,
823                    write_account_b,
824                    write_account_c,
825                ]),
826            );
827            // after block is completed
828            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, 3, bank3.bank_id());
829            assert_eq!(
830                vec![(1, 1), (2, 3), (3, 5)],
831                prioritization_fee_cache.get_prioritization_fees(&[]),
832            );
833            assert_eq!(
834                vec![(1, 2), (2, 3), (3, 6)],
835                prioritization_fee_cache.get_prioritization_fees(&[write_account_a]),
836            );
837            assert_eq!(
838                vec![(1, 2), (2, 4), (3, 5)],
839                prioritization_fee_cache.get_prioritization_fees(&[write_account_b]),
840            );
841            assert_eq!(
842                vec![(1, 1), (2, 4), (3, 6)],
843                prioritization_fee_cache.get_prioritization_fees(&[write_account_c]),
844            );
845            assert_eq!(
846                vec![(1, 2), (2, 4), (3, 6)],
847                prioritization_fee_cache
848                    .get_prioritization_fees(&[write_account_a, write_account_b]),
849            );
850            assert_eq!(
851                vec![(1, 2), (2, 4), (3, 6)],
852                prioritization_fee_cache.get_prioritization_fees(&[
853                    write_account_a,
854                    write_account_b,
855                    write_account_c,
856                ]),
857            );
858        }
859    }
860
861    #[test]
862    fn test_purge_duplicated_bank() {
863        // duplicated bank can exists for same slot before OC.
864        // prioritization_fee_cache should only have data from OC-ed bank
865        solana_logger::setup();
866        let write_account_a = Pubkey::new_unique();
867        let write_account_b = Pubkey::new_unique();
868        let write_account_c = Pubkey::new_unique();
869
870        let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
871        let bank0 = Bank::new_for_benches(&genesis_config);
872        let bank_forks = BankForks::new_rw_arc(bank0);
873        let bank = bank_forks.read().unwrap().working_bank();
874        let collector = solana_sdk::pubkey::new_rand();
875        let slot: Slot = 999;
876        let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, slot));
877        let bank2 = Arc::new(Bank::new_from_parent(bank, &collector, slot + 1));
878
879        let prioritization_fee_cache = PrioritizationFeeCache::default();
880
881        // Assert after add transactions for bank1 of slot 1
882        {
883            let txs = vec![
884                build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b),
885                build_sanitized_transaction_for_test(
886                    1,
887                    &Pubkey::new_unique(),
888                    &Pubkey::new_unique(),
889                ),
890            ];
891            sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter());
892        }
893
894        // Assert after add transactions for bank2 of slot 1
895        {
896            let txs = vec![
897                build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c),
898                build_sanitized_transaction_for_test(
899                    3,
900                    &Pubkey::new_unique(),
901                    &Pubkey::new_unique(),
902                ),
903            ];
904            sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter());
905        }
906
907        // Assert after finalize with bank1 of slot 1,
908        {
909            sync_finalize_priority_fee_for_test(&prioritization_fee_cache, slot, bank1.bank_id());
910
911            // and data available for query are from bank1
912            assert_eq!(
913                vec![(slot, 1)],
914                prioritization_fee_cache.get_prioritization_fees(&[])
915            );
916            assert_eq!(
917                vec![(slot, 2)],
918                prioritization_fee_cache.get_prioritization_fees(&[write_account_a])
919            );
920            assert_eq!(
921                vec![(slot, 2)],
922                prioritization_fee_cache.get_prioritization_fees(&[write_account_b])
923            );
924            assert_eq!(
925                vec![(slot, 1)],
926                prioritization_fee_cache.get_prioritization_fees(&[write_account_c])
927            );
928            assert_eq!(
929                vec![(slot, 2)],
930                prioritization_fee_cache
931                    .get_prioritization_fees(&[write_account_a, write_account_b])
932            );
933            assert_eq!(
934                vec![(slot, 2)],
935                prioritization_fee_cache.get_prioritization_fees(&[
936                    write_account_a,
937                    write_account_b,
938                    write_account_c
939                ])
940            );
941        }
942    }
943}