solana_cost_model/
cost_tracker.rs

1//! `cost_tracker` keeps tracking transaction cost per chained accounts as well as for entire block
2//! The main functions are:
3//! - would_fit(&tx_cost), immutable function to test if tx with tx_cost would fit into current block
4//! - add_transaction_cost(&tx_cost), mutable function to accumulate tx_cost to tracker.
5//!
6use {
7    crate::{block_cost_limits::*, transaction_cost::TransactionCost},
8    solana_metrics::datapoint_info,
9    solana_pubkey::Pubkey,
10    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
11    solana_transaction_error::TransactionError,
12    std::{cmp::Ordering, collections::HashMap, num::Saturating},
13};
14
15const WRITABLE_ACCOUNTS_PER_BLOCK: usize = 4096;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum CostTrackerError {
19    /// would exceed block max limit
20    WouldExceedBlockMaxLimit,
21
22    /// would exceed vote max limit
23    WouldExceedVoteMaxLimit,
24
25    /// would exceed account max limit
26    WouldExceedAccountMaxLimit,
27
28    /// would exceed account data block limit
29    WouldExceedAccountDataBlockLimit,
30
31    /// would exceed account data total limit
32    WouldExceedAccountDataTotalLimit,
33}
34
35impl From<CostTrackerError> for TransactionError {
36    fn from(err: CostTrackerError) -> Self {
37        match err {
38            CostTrackerError::WouldExceedBlockMaxLimit => Self::WouldExceedMaxBlockCostLimit,
39            CostTrackerError::WouldExceedVoteMaxLimit => Self::WouldExceedMaxVoteCostLimit,
40            CostTrackerError::WouldExceedAccountMaxLimit => Self::WouldExceedMaxAccountCostLimit,
41            CostTrackerError::WouldExceedAccountDataBlockLimit => {
42                Self::WouldExceedAccountDataBlockLimit
43            }
44            CostTrackerError::WouldExceedAccountDataTotalLimit => {
45                Self::WouldExceedAccountDataTotalLimit
46            }
47        }
48    }
49}
50
51/// Relevant block costs that were updated after successful `try_add()`
52#[derive(Debug, Default)]
53pub struct UpdatedCosts {
54    pub updated_block_cost: u64,
55    // for all write-locked accounts `try_add()` successfully updated, the highest account cost
56    // can be useful info.
57    pub updated_costliest_account_cost: u64,
58}
59
60#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
61#[derive(Debug)]
62pub struct CostTracker {
63    account_cost_limit: u64,
64    block_cost_limit: u64,
65    vote_cost_limit: u64,
66    cost_by_writable_accounts: HashMap<Pubkey, u64, ahash::RandomState>,
67    block_cost: u64,
68    vote_cost: u64,
69    transaction_count: Saturating<u64>,
70    allocated_accounts_data_size: Saturating<u64>,
71    transaction_signature_count: Saturating<u64>,
72    secp256k1_instruction_signature_count: Saturating<u64>,
73    ed25519_instruction_signature_count: Saturating<u64>,
74    /// The number of transactions that have had their estimated cost added to
75    /// the tracker, but are still waiting for an update with actual usage or
76    /// removal if the transaction does not end up getting committed.
77    in_flight_transaction_count: Saturating<usize>,
78    secp256r1_instruction_signature_count: Saturating<u64>,
79}
80
81impl Default for CostTracker {
82    fn default() -> Self {
83        // Clippy doesn't like asserts in const contexts, so need to explicitly allow them.  For
84        // more info, see this issue: https://github.com/rust-lang/rust-clippy/issues/8159
85        #![allow(clippy::assertions_on_constants)]
86        const _: () = assert!(MAX_WRITABLE_ACCOUNT_UNITS <= MAX_BLOCK_UNITS);
87        const _: () = assert!(MAX_VOTE_UNITS <= MAX_BLOCK_UNITS);
88
89        Self {
90            account_cost_limit: MAX_WRITABLE_ACCOUNT_UNITS,
91            block_cost_limit: MAX_BLOCK_UNITS,
92            vote_cost_limit: MAX_VOTE_UNITS,
93            cost_by_writable_accounts: HashMap::with_capacity_and_hasher(
94                WRITABLE_ACCOUNTS_PER_BLOCK,
95                ahash::RandomState::new(),
96            ),
97            block_cost: 0,
98            vote_cost: 0,
99            transaction_count: Saturating(0),
100            allocated_accounts_data_size: Saturating(0),
101            transaction_signature_count: Saturating(0),
102            secp256k1_instruction_signature_count: Saturating(0),
103            ed25519_instruction_signature_count: Saturating(0),
104            in_flight_transaction_count: Saturating(0),
105            secp256r1_instruction_signature_count: Saturating(0),
106        }
107    }
108}
109
110impl CostTracker {
111    pub fn new_from_parent_limits(&self) -> Self {
112        let mut new = Self::default();
113        new.set_limits(
114            self.account_cost_limit,
115            self.block_cost_limit,
116            self.vote_cost_limit,
117        );
118        new
119    }
120
121    pub fn reset(&mut self) {
122        self.cost_by_writable_accounts.clear();
123        self.block_cost = 0;
124        self.vote_cost = 0;
125        self.transaction_count = Saturating(0);
126        self.allocated_accounts_data_size = Saturating(0);
127        self.transaction_signature_count = Saturating(0);
128        self.secp256k1_instruction_signature_count = Saturating(0);
129        self.ed25519_instruction_signature_count = Saturating(0);
130        self.secp256r1_instruction_signature_count = Saturating(0);
131        self.in_flight_transaction_count = Saturating(0);
132    }
133
134    /// Get the overall block limit.
135    pub fn get_block_limit(&self) -> u64 {
136        self.block_cost_limit
137    }
138
139    /// allows to adjust limits initiated during construction
140    pub fn set_limits(
141        &mut self,
142        account_cost_limit: u64,
143        block_cost_limit: u64,
144        vote_cost_limit: u64,
145    ) {
146        self.account_cost_limit = account_cost_limit;
147        self.block_cost_limit = block_cost_limit;
148        self.vote_cost_limit = vote_cost_limit;
149    }
150
151    pub fn in_flight_transaction_count(&self) -> usize {
152        self.in_flight_transaction_count.0
153    }
154
155    pub fn add_transactions_in_flight(&mut self, in_flight_transaction_count: usize) {
156        self.in_flight_transaction_count += in_flight_transaction_count;
157    }
158
159    pub fn sub_transactions_in_flight(&mut self, in_flight_transaction_count: usize) {
160        self.in_flight_transaction_count -= in_flight_transaction_count
161    }
162
163    pub fn try_add(
164        &mut self,
165        tx_cost: &TransactionCost<impl TransactionWithMeta>,
166    ) -> Result<UpdatedCosts, CostTrackerError> {
167        self.would_fit(tx_cost)?;
168        let updated_costliest_account_cost = self.add_transaction_cost(tx_cost);
169        Ok(UpdatedCosts {
170            updated_block_cost: self.block_cost,
171            updated_costliest_account_cost,
172        })
173    }
174
175    pub fn update_execution_cost(
176        &mut self,
177        estimated_tx_cost: &TransactionCost<impl TransactionWithMeta>,
178        actual_execution_units: u64,
179        actual_loaded_accounts_data_size_cost: u64,
180    ) {
181        let actual_load_and_execution_units =
182            actual_execution_units.saturating_add(actual_loaded_accounts_data_size_cost);
183        let estimated_load_and_execution_units = estimated_tx_cost
184            .programs_execution_cost()
185            .saturating_add(estimated_tx_cost.loaded_accounts_data_size_cost());
186        match actual_load_and_execution_units.cmp(&estimated_load_and_execution_units) {
187            Ordering::Equal => (),
188            Ordering::Greater => {
189                self.add_transaction_execution_cost(
190                    estimated_tx_cost,
191                    actual_load_and_execution_units - estimated_load_and_execution_units,
192                );
193            }
194            Ordering::Less => {
195                self.sub_transaction_execution_cost(
196                    estimated_tx_cost,
197                    estimated_load_and_execution_units - actual_load_and_execution_units,
198                );
199            }
200        }
201    }
202
203    pub fn remove(&mut self, tx_cost: &TransactionCost<impl TransactionWithMeta>) {
204        self.remove_transaction_cost(tx_cost);
205    }
206
207    pub fn block_cost(&self) -> u64 {
208        self.block_cost
209    }
210
211    pub fn vote_cost(&self) -> u64 {
212        self.vote_cost
213    }
214
215    pub fn transaction_count(&self) -> u64 {
216        self.transaction_count.0
217    }
218
219    pub fn report_stats(&self, bank_slot: solana_clock::Slot) {
220        // skip reporting if block is empty
221        if self.transaction_count.0 == 0 {
222            return;
223        }
224
225        let (costliest_account, costliest_account_cost) = self.find_costliest_account();
226
227        datapoint_info!(
228            "cost_tracker_stats",
229            ("bank_slot", bank_slot as i64, i64),
230            ("block_cost", self.block_cost as i64, i64),
231            ("vote_cost", self.vote_cost as i64, i64),
232            ("transaction_count", self.transaction_count.0 as i64, i64),
233            ("number_of_accounts", self.number_of_accounts() as i64, i64),
234            ("costliest_account", costliest_account.to_string(), String),
235            ("costliest_account_cost", costliest_account_cost as i64, i64),
236            (
237                "allocated_accounts_data_size",
238                self.allocated_accounts_data_size.0,
239                i64
240            ),
241            (
242                "transaction_signature_count",
243                self.transaction_signature_count.0,
244                i64
245            ),
246            (
247                "secp256k1_instruction_signature_count",
248                self.secp256k1_instruction_signature_count.0,
249                i64
250            ),
251            (
252                "ed25519_instruction_signature_count",
253                self.ed25519_instruction_signature_count.0,
254                i64
255            ),
256            (
257                "inflight_transaction_count",
258                self.in_flight_transaction_count.0,
259                i64
260            ),
261            (
262                "secp256r1_instruction_signature_count",
263                self.secp256r1_instruction_signature_count.0,
264                i64
265            )
266        );
267    }
268
269    fn find_costliest_account(&self) -> (Pubkey, u64) {
270        self.cost_by_writable_accounts
271            .iter()
272            .max_by_key(|(_, &cost)| cost)
273            .map(|(&pubkey, &cost)| (pubkey, cost))
274            .unwrap_or_default()
275    }
276
277    fn would_fit(
278        &self,
279        tx_cost: &TransactionCost<impl TransactionWithMeta>,
280    ) -> Result<(), CostTrackerError> {
281        let cost: u64 = tx_cost.sum();
282
283        if tx_cost.is_simple_vote() {
284            // if vote transaction, check if it exceeds vote_transaction_limit
285            if self.vote_cost.saturating_add(cost) > self.vote_cost_limit {
286                return Err(CostTrackerError::WouldExceedVoteMaxLimit);
287            }
288        }
289
290        if self.block_cost.saturating_add(cost) > self.block_cost_limit {
291            // check against the total package cost
292            return Err(CostTrackerError::WouldExceedBlockMaxLimit);
293        }
294
295        // check if the transaction itself is more costly than the account_cost_limit
296        if cost > self.account_cost_limit {
297            return Err(CostTrackerError::WouldExceedAccountMaxLimit);
298        }
299
300        let allocated_accounts_data_size =
301            self.allocated_accounts_data_size + Saturating(tx_cost.allocated_accounts_data_size());
302
303        if allocated_accounts_data_size.0 > MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA {
304            return Err(CostTrackerError::WouldExceedAccountDataBlockLimit);
305        }
306
307        // check each account against account_cost_limit,
308        for account_key in tx_cost.writable_accounts() {
309            match self.cost_by_writable_accounts.get(account_key) {
310                Some(chained_cost) => {
311                    if chained_cost.saturating_add(cost) > self.account_cost_limit {
312                        return Err(CostTrackerError::WouldExceedAccountMaxLimit);
313                    } else {
314                        continue;
315                    }
316                }
317                None => continue,
318            }
319        }
320
321        Ok(())
322    }
323
324    // Returns the highest account cost for all write-lock accounts `TransactionCost` updated
325    fn add_transaction_cost(&mut self, tx_cost: &TransactionCost<impl TransactionWithMeta>) -> u64 {
326        self.allocated_accounts_data_size += tx_cost.allocated_accounts_data_size();
327        self.transaction_count += 1;
328        self.transaction_signature_count += tx_cost.num_transaction_signatures();
329        self.secp256k1_instruction_signature_count +=
330            tx_cost.num_secp256k1_instruction_signatures();
331        self.ed25519_instruction_signature_count += tx_cost.num_ed25519_instruction_signatures();
332        self.secp256r1_instruction_signature_count +=
333            tx_cost.num_secp256r1_instruction_signatures();
334        self.add_transaction_execution_cost(tx_cost, tx_cost.sum())
335    }
336
337    fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost<impl TransactionWithMeta>) {
338        let cost = tx_cost.sum();
339        self.sub_transaction_execution_cost(tx_cost, cost);
340        self.allocated_accounts_data_size -= tx_cost.allocated_accounts_data_size();
341        self.transaction_count -= 1;
342        self.transaction_signature_count -= tx_cost.num_transaction_signatures();
343        self.secp256k1_instruction_signature_count -=
344            tx_cost.num_secp256k1_instruction_signatures();
345        self.ed25519_instruction_signature_count -= tx_cost.num_ed25519_instruction_signatures();
346        self.secp256r1_instruction_signature_count -=
347            tx_cost.num_secp256r1_instruction_signatures();
348    }
349
350    /// Apply additional actual execution units to cost_tracker
351    /// Return the costliest account cost that were updated by `TransactionCost`
352    fn add_transaction_execution_cost(
353        &mut self,
354        tx_cost: &TransactionCost<impl TransactionWithMeta>,
355        adjustment: u64,
356    ) -> u64 {
357        let mut costliest_account_cost = 0;
358        for account_key in tx_cost.writable_accounts() {
359            let account_cost = self
360                .cost_by_writable_accounts
361                .entry(*account_key)
362                .or_insert(0);
363            *account_cost = account_cost.saturating_add(adjustment);
364            costliest_account_cost = costliest_account_cost.max(*account_cost);
365        }
366        self.block_cost = self.block_cost.saturating_add(adjustment);
367        if tx_cost.is_simple_vote() {
368            self.vote_cost = self.vote_cost.saturating_add(adjustment);
369        }
370
371        costliest_account_cost
372    }
373
374    /// Subtract extra execution units from cost_tracker
375    fn sub_transaction_execution_cost(
376        &mut self,
377        tx_cost: &TransactionCost<impl TransactionWithMeta>,
378        adjustment: u64,
379    ) {
380        for account_key in tx_cost.writable_accounts() {
381            let account_cost = self
382                .cost_by_writable_accounts
383                .entry(*account_key)
384                .or_insert(0);
385            *account_cost = account_cost.saturating_sub(adjustment);
386        }
387        self.block_cost = self.block_cost.saturating_sub(adjustment);
388        if tx_cost.is_simple_vote() {
389            self.vote_cost = self.vote_cost.saturating_sub(adjustment);
390        }
391    }
392
393    /// count number of none-zero CU accounts
394    fn number_of_accounts(&self) -> usize {
395        self.cost_by_writable_accounts
396            .values()
397            .filter(|units| **units > 0)
398            .count()
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use {
405        super::*,
406        crate::transaction_cost::{WritableKeysTransaction, *},
407        solana_keypair::Keypair,
408        solana_signer::Signer,
409        std::cmp,
410    };
411
412    impl CostTracker {
413        fn new(account_cost_limit: u64, block_cost_limit: u64, vote_cost_limit: u64) -> Self {
414            assert!(account_cost_limit <= block_cost_limit);
415            assert!(vote_cost_limit <= block_cost_limit);
416            Self {
417                account_cost_limit,
418                block_cost_limit,
419                vote_cost_limit,
420                ..Self::default()
421            }
422        }
423    }
424
425    fn test_setup() -> Keypair {
426        solana_logger::setup();
427        Keypair::new()
428    }
429
430    fn build_simple_transaction(mint_keypair: &Keypair) -> WritableKeysTransaction {
431        WritableKeysTransaction(vec![mint_keypair.pubkey()])
432    }
433
434    fn simple_usage_cost_details(
435        transaction: &WritableKeysTransaction,
436        programs_execution_cost: u64,
437    ) -> UsageCostDetails<WritableKeysTransaction> {
438        UsageCostDetails {
439            transaction,
440            signature_cost: 0,
441            write_lock_cost: 0,
442            data_bytes_cost: 0,
443            programs_execution_cost,
444            loaded_accounts_data_size_cost: 0,
445            allocated_accounts_data_size: 0,
446        }
447    }
448
449    fn simple_transaction_cost(
450        transaction: &WritableKeysTransaction,
451        programs_execution_cost: u64,
452    ) -> TransactionCost<WritableKeysTransaction> {
453        TransactionCost::Transaction(simple_usage_cost_details(
454            transaction,
455            programs_execution_cost,
456        ))
457    }
458
459    fn simple_vote_transaction_cost(
460        transaction: &WritableKeysTransaction,
461    ) -> TransactionCost<WritableKeysTransaction> {
462        TransactionCost::SimpleVote { transaction }
463    }
464
465    #[test]
466    fn test_cost_tracker_initialization() {
467        let testee = CostTracker::new(10, 11, 8);
468        assert_eq!(10, testee.account_cost_limit);
469        assert_eq!(11, testee.block_cost_limit);
470        assert_eq!(8, testee.vote_cost_limit);
471        assert_eq!(0, testee.cost_by_writable_accounts.len());
472        assert_eq!(0, testee.block_cost);
473    }
474
475    #[test]
476    fn test_cost_tracker_ok_add_one() {
477        let mint_keypair = test_setup();
478        let tx = build_simple_transaction(&mint_keypair);
479        let tx_cost = simple_transaction_cost(&tx, 5);
480        let cost = tx_cost.sum();
481
482        // build testee to have capacity for one simple transaction
483        let mut testee = CostTracker::new(cost, cost, cost);
484        assert!(testee.would_fit(&tx_cost).is_ok());
485        testee.add_transaction_cost(&tx_cost);
486        assert_eq!(cost, testee.block_cost);
487        assert_eq!(0, testee.vote_cost);
488        let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
489        assert_eq!(cost, costliest_account_cost);
490    }
491
492    #[test]
493    fn test_cost_tracker_ok_add_one_vote() {
494        let mint_keypair = test_setup();
495        let tx = build_simple_transaction(&mint_keypair);
496        let tx_cost = simple_vote_transaction_cost(&tx);
497        let cost = tx_cost.sum();
498
499        // build testee to have capacity for one simple transaction
500        let mut testee = CostTracker::new(cost, cost, cost);
501        assert!(testee.would_fit(&tx_cost).is_ok());
502        testee.add_transaction_cost(&tx_cost);
503        assert_eq!(cost, testee.block_cost);
504        assert_eq!(cost, testee.vote_cost);
505        let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
506        assert_eq!(cost, costliest_account_cost);
507    }
508
509    #[test]
510    fn test_cost_tracker_add_data() {
511        let mint_keypair = test_setup();
512        let tx = build_simple_transaction(&mint_keypair);
513        let mut tx_cost = simple_transaction_cost(&tx, 5);
514        if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost {
515            usage_cost.allocated_accounts_data_size = 1;
516        } else {
517            unreachable!();
518        }
519        let cost = tx_cost.sum();
520
521        // build testee to have capacity for one simple transaction
522        let mut testee = CostTracker::new(cost, cost, cost);
523        assert!(testee.would_fit(&tx_cost).is_ok());
524        let old = testee.allocated_accounts_data_size;
525        testee.add_transaction_cost(&tx_cost);
526        assert_eq!(old.0 + 1, testee.allocated_accounts_data_size.0);
527    }
528
529    #[test]
530    fn test_cost_tracker_ok_add_two_same_accounts() {
531        let mint_keypair = test_setup();
532        // build two transactions with same signed account
533        let tx1 = build_simple_transaction(&mint_keypair);
534        let tx_cost1 = simple_transaction_cost(&tx1, 5);
535        let cost1 = tx_cost1.sum();
536        let tx2 = build_simple_transaction(&mint_keypair);
537        let tx_cost2 = simple_transaction_cost(&tx2, 5);
538        let cost2 = tx_cost2.sum();
539
540        // build testee to have capacity for two simple transactions, with same accounts
541        let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2, cost1 + cost2);
542        {
543            assert!(testee.would_fit(&tx_cost1).is_ok());
544            testee.add_transaction_cost(&tx_cost1);
545        }
546        {
547            assert!(testee.would_fit(&tx_cost2).is_ok());
548            testee.add_transaction_cost(&tx_cost2);
549        }
550        assert_eq!(cost1 + cost2, testee.block_cost);
551        assert_eq!(1, testee.cost_by_writable_accounts.len());
552        let (_ccostliest_account, costliest_account_cost) = testee.find_costliest_account();
553        assert_eq!(cost1 + cost2, costliest_account_cost);
554    }
555
556    #[test]
557    fn test_cost_tracker_ok_add_two_diff_accounts() {
558        let mint_keypair = test_setup();
559        // build two transactions with diff accounts
560        let second_account = Keypair::new();
561        let tx1 = build_simple_transaction(&mint_keypair);
562        let tx_cost1 = simple_transaction_cost(&tx1, 5);
563        let cost1 = tx_cost1.sum();
564
565        let tx2 = build_simple_transaction(&second_account);
566        let tx_cost2 = simple_transaction_cost(&tx2, 5);
567        let cost2 = tx_cost2.sum();
568
569        // build testee to have capacity for two simple transactions, with same accounts
570        let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2, cost1 + cost2);
571        {
572            assert!(testee.would_fit(&tx_cost1).is_ok());
573            testee.add_transaction_cost(&tx_cost1);
574        }
575        {
576            assert!(testee.would_fit(&tx_cost2).is_ok());
577            testee.add_transaction_cost(&tx_cost2);
578        }
579        assert_eq!(cost1 + cost2, testee.block_cost);
580        assert_eq!(2, testee.cost_by_writable_accounts.len());
581        let (_ccostliest_account, costliest_account_cost) = testee.find_costliest_account();
582        assert_eq!(std::cmp::max(cost1, cost2), costliest_account_cost);
583    }
584
585    #[test]
586    fn test_cost_tracker_chain_reach_limit() {
587        let mint_keypair = test_setup();
588        // build two transactions with same signed account
589        let tx1 = build_simple_transaction(&mint_keypair);
590        let tx_cost1 = simple_transaction_cost(&tx1, 5);
591        let cost1 = tx_cost1.sum();
592        let tx2 = build_simple_transaction(&mint_keypair);
593        let tx_cost2 = simple_transaction_cost(&tx2, 5);
594        let cost2 = tx_cost2.sum();
595
596        // build testee to have capacity for two simple transactions, but not for same accounts
597        let mut testee = CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2, cost1 + cost2);
598        // should have room for first transaction
599        {
600            assert!(testee.would_fit(&tx_cost1).is_ok());
601            testee.add_transaction_cost(&tx_cost1);
602        }
603        // but no more sapce on the same chain (same signer account)
604        {
605            assert!(testee.would_fit(&tx_cost2).is_err());
606        }
607    }
608
609    #[test]
610    fn test_cost_tracker_reach_limit() {
611        let mint_keypair = test_setup();
612        // build two transactions with diff accounts
613        let second_account = Keypair::new();
614        let tx1 = build_simple_transaction(&mint_keypair);
615        let tx_cost1 = simple_transaction_cost(&tx1, 5);
616        let cost1 = tx_cost1.sum();
617        let tx2 = build_simple_transaction(&second_account);
618        let tx_cost2 = simple_transaction_cost(&tx2, 5);
619        let cost2 = tx_cost2.sum();
620
621        // build testee to have capacity for each chain, but not enough room for both transactions
622        let mut testee =
623            CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1, cost1 + cost2 - 1);
624        // should have room for first transaction
625        {
626            assert!(testee.would_fit(&tx_cost1).is_ok());
627            testee.add_transaction_cost(&tx_cost1);
628        }
629        // but no more room for package as whole
630        {
631            assert!(testee.would_fit(&tx_cost2).is_err());
632        }
633    }
634
635    #[test]
636    fn test_cost_tracker_reach_vote_limit() {
637        let mint_keypair = test_setup();
638        // build two mocking vote transactions with diff accounts
639        let second_account = Keypair::new();
640        let tx1 = build_simple_transaction(&mint_keypair);
641        let tx_cost1 = simple_vote_transaction_cost(&tx1);
642        let cost1 = tx_cost1.sum();
643        let tx2 = build_simple_transaction(&second_account);
644        let tx_cost2 = simple_vote_transaction_cost(&tx2);
645        let cost2 = tx_cost2.sum();
646
647        // build testee to have capacity for each chain, but not enough room for both votes
648        let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2, cost1 + cost2 - 1);
649        // should have room for first vote
650        {
651            assert!(testee.would_fit(&tx_cost1).is_ok());
652            testee.add_transaction_cost(&tx_cost1);
653        }
654        // but no more room for package as whole
655        {
656            assert!(testee.would_fit(&tx_cost2).is_err());
657        }
658        // however there is room for none-vote tx3
659        {
660            let third_account = Keypair::new();
661            let tx3 = build_simple_transaction(&third_account);
662            let tx_cost3 = simple_transaction_cost(&tx3, 5);
663            assert!(testee.would_fit(&tx_cost3).is_ok());
664        }
665    }
666
667    #[test]
668    fn test_cost_tracker_reach_data_block_limit() {
669        let mint_keypair = test_setup();
670        // build two transactions with diff accounts
671        let second_account = Keypair::new();
672        let tx1 = build_simple_transaction(&mint_keypair);
673        let mut tx_cost1 = simple_transaction_cost(&tx1, 5);
674        let tx2 = build_simple_transaction(&second_account);
675        let mut tx_cost2 = simple_transaction_cost(&tx2, 5);
676        if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 {
677            usage_cost.allocated_accounts_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA;
678        } else {
679            unreachable!();
680        }
681        if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 {
682            usage_cost.allocated_accounts_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1;
683        } else {
684            unreachable!();
685        }
686        let cost1 = tx_cost1.sum();
687        let cost2 = tx_cost2.sum();
688
689        // build testee that passes
690        let testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1, cost1 + cost2 - 1);
691        assert!(testee.would_fit(&tx_cost1).is_ok());
692        // data is too big
693        assert_eq!(
694            testee.would_fit(&tx_cost2),
695            Err(CostTrackerError::WouldExceedAccountDataBlockLimit),
696        );
697    }
698
699    #[test]
700    fn test_cost_tracker_remove() {
701        let mint_keypair = test_setup();
702        // build two transactions with diff accounts
703        let second_account = Keypair::new();
704        let tx1 = build_simple_transaction(&mint_keypair);
705        let tx_cost1 = simple_transaction_cost(&tx1, 5);
706        let tx2 = build_simple_transaction(&second_account);
707        let tx_cost2 = simple_transaction_cost(&tx2, 5);
708        let cost1 = tx_cost1.sum();
709        let cost2 = tx_cost2.sum();
710
711        // build testee
712        let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2, cost1 + cost2);
713
714        assert!(testee.try_add(&tx_cost1).is_ok());
715        assert!(testee.try_add(&tx_cost2).is_ok());
716        assert_eq!(cost1 + cost2, testee.block_cost);
717
718        // removing a tx_cost affects block_cost
719        testee.remove(&tx_cost1);
720        assert_eq!(cost2, testee.block_cost);
721
722        // add back tx1
723        assert!(testee.try_add(&tx_cost1).is_ok());
724        assert_eq!(cost1 + cost2, testee.block_cost);
725
726        // cannot add tx1 again, cost limit would be exceeded
727        assert!(testee.try_add(&tx_cost1).is_err());
728    }
729
730    #[test]
731    fn test_cost_tracker_try_add_is_atomic() {
732        let acct1 = Pubkey::new_unique();
733        let acct2 = Pubkey::new_unique();
734        let acct3 = Pubkey::new_unique();
735        let cost = 100;
736        let account_max = cost * 2;
737        let block_max = account_max * 3; // for three accts
738
739        let mut testee = CostTracker::new(account_max, block_max, block_max);
740
741        // case 1: a tx writes to 3 accounts, should success, we will have:
742        // | acct1 | $cost |
743        // | acct2 | $cost |
744        // | acct3 | $cost |
745        // and block_cost = $cost
746        {
747            let transaction = WritableKeysTransaction(vec![acct1, acct2, acct3]);
748            let tx_cost = simple_transaction_cost(&transaction, cost);
749            assert!(testee.try_add(&tx_cost).is_ok());
750            let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
751            assert_eq!(cost, testee.block_cost);
752            assert_eq!(3, testee.cost_by_writable_accounts.len());
753            assert_eq!(cost, costliest_account_cost);
754        }
755
756        // case 2: add tx writes to acct2 with $cost, should succeed, result to
757        // | acct1 | $cost |
758        // | acct2 | $cost * 2 |
759        // | acct3 | $cost |
760        // and block_cost = $cost * 2
761        {
762            let transaction = WritableKeysTransaction(vec![acct2]);
763            let tx_cost = simple_transaction_cost(&transaction, cost);
764            assert!(testee.try_add(&tx_cost).is_ok());
765            let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
766            assert_eq!(cost * 2, testee.block_cost);
767            assert_eq!(3, testee.cost_by_writable_accounts.len());
768            assert_eq!(cost * 2, costliest_account_cost);
769            assert_eq!(acct2, costliest_account);
770        }
771
772        // case 3: add tx writes to [acct1, acct2], acct2 exceeds limit, should failed atomically,
773        // we should still have:
774        // | acct1 | $cost |
775        // | acct2 | $cost * 2 |
776        // | acct3 | $cost |
777        // and block_cost = $cost * 2
778        {
779            let transaction = WritableKeysTransaction(vec![acct1, acct2]);
780            let tx_cost = simple_transaction_cost(&transaction, cost);
781            assert!(testee.try_add(&tx_cost).is_err());
782            let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
783            assert_eq!(cost * 2, testee.block_cost);
784            assert_eq!(3, testee.cost_by_writable_accounts.len());
785            assert_eq!(cost * 2, costliest_account_cost);
786            assert_eq!(acct2, costliest_account);
787        }
788    }
789
790    #[test]
791    fn test_adjust_transaction_execution_cost() {
792        let acct1 = Pubkey::new_unique();
793        let acct2 = Pubkey::new_unique();
794        let acct3 = Pubkey::new_unique();
795        let cost = 100;
796        let account_max = cost * 2;
797        let block_max = account_max * 3; // for three accts
798
799        let mut testee = CostTracker::new(account_max, block_max, block_max);
800        let transaction = WritableKeysTransaction(vec![acct1, acct2, acct3]);
801        let tx_cost = simple_transaction_cost(&transaction, cost);
802        let mut expected_block_cost = tx_cost.sum();
803        let expected_tx_count = 1;
804        assert!(testee.try_add(&tx_cost).is_ok());
805        assert_eq!(expected_block_cost, testee.block_cost());
806        assert_eq!(expected_tx_count, testee.transaction_count());
807        testee
808            .cost_by_writable_accounts
809            .iter()
810            .for_each(|(_key, units)| {
811                assert_eq!(expected_block_cost, *units);
812            });
813
814        // adjust up
815        {
816            let adjustment = 50u64;
817            testee.add_transaction_execution_cost(&tx_cost, adjustment);
818            expected_block_cost += 50;
819            assert_eq!(expected_block_cost, testee.block_cost());
820            assert_eq!(expected_tx_count, testee.transaction_count());
821            testee
822                .cost_by_writable_accounts
823                .iter()
824                .for_each(|(_key, units)| {
825                    assert_eq!(expected_block_cost, *units);
826                });
827        }
828
829        // adjust down
830        {
831            let adjustment = 50u64;
832            testee.sub_transaction_execution_cost(&tx_cost, adjustment);
833            expected_block_cost -= 50;
834            assert_eq!(expected_block_cost, testee.block_cost());
835            assert_eq!(expected_tx_count, testee.transaction_count());
836            testee
837                .cost_by_writable_accounts
838                .iter()
839                .for_each(|(_key, units)| {
840                    assert_eq!(expected_block_cost, *units);
841                });
842        }
843
844        // adjust overflow
845        {
846            testee.add_transaction_execution_cost(&tx_cost, u64::MAX);
847            // expect block cost set to limit
848            assert_eq!(u64::MAX, testee.block_cost());
849            assert_eq!(expected_tx_count, testee.transaction_count());
850            testee
851                .cost_by_writable_accounts
852                .iter()
853                .for_each(|(_key, units)| {
854                    assert_eq!(u64::MAX, *units);
855                });
856        }
857
858        // adjust underflow
859        {
860            testee.sub_transaction_execution_cost(&tx_cost, u64::MAX);
861            // expect block cost set to limit
862            assert_eq!(u64::MIN, testee.block_cost());
863            assert_eq!(expected_tx_count, testee.transaction_count());
864            testee
865                .cost_by_writable_accounts
866                .iter()
867                .for_each(|(_key, units)| {
868                    assert_eq!(u64::MIN, *units);
869                });
870            // assert the number of non-empty accounts is zero, but map
871            // still contains 3 account
872            assert_eq!(0, testee.number_of_accounts());
873            assert_eq!(3, testee.cost_by_writable_accounts.len());
874        }
875    }
876
877    #[test]
878    fn test_update_execution_cost() {
879        let estimated_programs_execution_cost = 100;
880        let estimated_loaded_accounts_data_size_cost = 200;
881        let number_writeble_accounts = 3;
882        let transaction = WritableKeysTransaction(
883            std::iter::repeat_with(Pubkey::new_unique)
884                .take(number_writeble_accounts)
885                .collect(),
886        );
887
888        let mut usage_cost =
889            simple_usage_cost_details(&transaction, estimated_programs_execution_cost);
890        usage_cost.loaded_accounts_data_size_cost = estimated_loaded_accounts_data_size_cost;
891        let tx_cost = TransactionCost::Transaction(usage_cost);
892        // confirm tx_cost is only made up by programs_execution_cost and
893        // loaded_accounts_data_size_cost
894        let estimated_tx_cost = tx_cost.sum();
895        assert_eq!(
896            estimated_tx_cost,
897            estimated_programs_execution_cost + estimated_loaded_accounts_data_size_cost
898        );
899
900        let test_update_cost_tracker =
901            |execution_cost_adjust: i64, loaded_acounts_data_size_cost_adjust: i64| {
902                let mut cost_tracker = CostTracker::default();
903                assert!(cost_tracker.try_add(&tx_cost).is_ok());
904
905                let actual_programs_execution_cost =
906                    (estimated_programs_execution_cost as i64 + execution_cost_adjust) as u64;
907                let actual_loaded_accounts_data_size_cost =
908                    (estimated_loaded_accounts_data_size_cost as i64
909                        + loaded_acounts_data_size_cost_adjust) as u64;
910                let expected_cost = (estimated_tx_cost as i64
911                    + execution_cost_adjust
912                    + loaded_acounts_data_size_cost_adjust)
913                    as u64;
914
915                cost_tracker.update_execution_cost(
916                    &tx_cost,
917                    actual_programs_execution_cost,
918                    actual_loaded_accounts_data_size_cost,
919                );
920
921                assert_eq!(expected_cost, cost_tracker.block_cost);
922                assert_eq!(0, cost_tracker.vote_cost);
923                assert_eq!(
924                    number_writeble_accounts,
925                    cost_tracker.cost_by_writable_accounts.len()
926                );
927                for writable_account_cost in cost_tracker.cost_by_writable_accounts.values() {
928                    assert_eq!(expected_cost, *writable_account_cost);
929                }
930                assert_eq!(1, cost_tracker.transaction_count.0);
931            };
932
933        test_update_cost_tracker(0, 0);
934        test_update_cost_tracker(0, 9);
935        test_update_cost_tracker(0, -9);
936        test_update_cost_tracker(9, 0);
937        test_update_cost_tracker(9, 9);
938        test_update_cost_tracker(9, -9);
939        test_update_cost_tracker(-9, 0);
940        test_update_cost_tracker(-9, 9);
941        test_update_cost_tracker(-9, -9);
942    }
943
944    #[test]
945    fn test_remove_transaction_cost() {
946        let mut cost_tracker = CostTracker::default();
947
948        let cost = 100u64;
949        let transaction = WritableKeysTransaction(vec![Pubkey::new_unique()]);
950        let tx_cost = simple_transaction_cost(&transaction, cost);
951        cost_tracker.add_transaction_cost(&tx_cost);
952        // assert cost_tracker is reverted to default
953        assert_eq!(1, cost_tracker.transaction_count.0);
954        assert_eq!(1, cost_tracker.number_of_accounts());
955        assert_eq!(cost, cost_tracker.block_cost);
956        assert_eq!(0, cost_tracker.vote_cost);
957        assert_eq!(0, cost_tracker.allocated_accounts_data_size.0);
958
959        cost_tracker.remove_transaction_cost(&tx_cost);
960        // assert cost_tracker is reverted to default
961        assert_eq!(0, cost_tracker.transaction_count.0);
962        assert_eq!(0, cost_tracker.number_of_accounts());
963        assert_eq!(0, cost_tracker.block_cost);
964        assert_eq!(0, cost_tracker.vote_cost);
965        assert_eq!(0, cost_tracker.allocated_accounts_data_size.0);
966    }
967}