Skip to main content

solana_runtime/
prioritization_fee_cache.rs

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