miraland_runtime/
prioritization_fee_cache.rs

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