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