1use {
7 crate::{block_cost_limits::*, cost_model::TransactionCost},
8 solana_sdk::{clock::Slot, pubkey::Pubkey, saturating_add_assign},
9 std::{cmp::Ordering, collections::HashMap},
10};
11
12const WRITABLE_ACCOUNTS_PER_BLOCK: usize = 512;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CostTrackerError {
16 WouldExceedBlockMaxLimit,
18
19 WouldExceedVoteMaxLimit,
21
22 WouldExceedAccountMaxLimit,
24
25 WouldExceedAccountDataBlockLimit,
27
28 WouldExceedAccountDataTotalLimit,
30}
31
32#[derive(AbiExample, Debug)]
33pub struct CostTracker {
34 account_cost_limit: u64,
35 block_cost_limit: u64,
36 vote_cost_limit: u64,
37 cost_by_writable_accounts: HashMap<Pubkey, u64>,
38 block_cost: u64,
39 vote_cost: u64,
40 transaction_count: u64,
41 account_data_size: u64,
42
43 account_data_size_limit: Option<u64>,
46}
47
48impl Default for CostTracker {
49 fn default() -> Self {
50 #![allow(clippy::assertions_on_constants)]
53 const _: () = assert!(MAX_WRITABLE_ACCOUNT_UNITS <= MAX_BLOCK_UNITS);
54 const _: () = assert!(MAX_VOTE_UNITS <= MAX_BLOCK_UNITS);
55
56 Self {
57 account_cost_limit: MAX_WRITABLE_ACCOUNT_UNITS,
58 block_cost_limit: MAX_BLOCK_UNITS,
59 vote_cost_limit: MAX_VOTE_UNITS,
60 cost_by_writable_accounts: HashMap::with_capacity(WRITABLE_ACCOUNTS_PER_BLOCK),
61 block_cost: 0,
62 vote_cost: 0,
63 transaction_count: 0,
64 account_data_size: 0,
65 account_data_size_limit: None,
66 }
67 }
68}
69
70impl CostTracker {
71 #[must_use]
73 pub fn new_with_account_data_size_limit(account_data_size_limit: Option<u64>) -> Self {
74 Self {
75 account_data_size_limit,
76 ..Self::default()
77 }
78 }
79
80 pub fn set_limits(
82 &mut self,
83 account_cost_limit: u64,
84 block_cost_limit: u64,
85 vote_cost_limit: u64,
86 ) {
87 self.account_cost_limit = account_cost_limit;
88 self.block_cost_limit = block_cost_limit;
89 self.vote_cost_limit = vote_cost_limit;
90 }
91
92 pub fn try_add(&mut self, tx_cost: &TransactionCost) -> Result<u64, CostTrackerError> {
93 self.would_fit(tx_cost)?;
94 self.add_transaction_cost(tx_cost);
95 Ok(self.block_cost)
96 }
97
98 pub fn try_add_requested_cus(
100 &mut self,
101 write_lock_accounts: &[Pubkey],
102 requested_cus: u64,
103 is_vote: bool,
104 ) -> Result<u64, CostTrackerError> {
105 self.would_fit_internal(write_lock_accounts.iter(), requested_cus, is_vote, 0)?;
106 self.add_transaction_cost_internal(write_lock_accounts.iter(), requested_cus, is_vote, 0);
107 Ok(self.block_cost)
108 }
109
110 pub fn update_execution_cost(
111 &mut self,
112 estimated_tx_cost: &TransactionCost,
113 actual_execution_units: u64,
114 ) {
115 let estimated_execution_units = estimated_tx_cost.bpf_execution_cost;
116 match actual_execution_units.cmp(&estimated_execution_units) {
117 Ordering::Equal => (),
118 Ordering::Greater => {
119 self.add_transaction_execution_cost(
120 estimated_tx_cost,
121 actual_execution_units - estimated_execution_units,
122 );
123 }
124 Ordering::Less => {
125 self.sub_transaction_execution_cost(
126 estimated_tx_cost,
127 estimated_execution_units - actual_execution_units,
128 );
129 }
130 }
131 }
132
133 pub fn remove(&mut self, tx_cost: &TransactionCost) {
134 self.remove_transaction_cost(tx_cost);
135 }
136
137 pub fn block_cost(&self) -> u64 {
138 self.block_cost
139 }
140
141 pub fn transaction_count(&self) -> u64 {
142 self.transaction_count
143 }
144
145 pub fn report_stats(&self, bank_slot: Slot) {
146 if self.transaction_count == 0 {
148 return;
149 }
150
151 let (costliest_account, costliest_account_cost) = self.find_costliest_account();
152
153 datapoint_info!(
154 "cost_tracker_stats",
155 ("bank_slot", bank_slot as i64, i64),
156 ("block_cost", self.block_cost as i64, i64),
157 ("vote_cost", self.vote_cost as i64, i64),
158 ("transaction_count", self.transaction_count as i64, i64),
159 ("number_of_accounts", self.number_of_accounts() as i64, i64),
160 ("costliest_account", costliest_account.to_string(), String),
161 ("costliest_account_cost", costliest_account_cost as i64, i64),
162 ("account_data_size", self.account_data_size, i64),
163 );
164 }
165
166 fn find_costliest_account(&self) -> (Pubkey, u64) {
167 self.cost_by_writable_accounts
168 .iter()
169 .max_by_key(|(_, &cost)| cost)
170 .map(|(&pubkey, &cost)| (pubkey, cost))
171 .unwrap_or_default()
172 }
173
174 fn would_fit(&self, tx_cost: &TransactionCost) -> Result<(), CostTrackerError> {
175 self.would_fit_internal(
176 tx_cost.writable_accounts.iter(),
177 tx_cost.sum(),
178 tx_cost.is_simple_vote,
179 tx_cost.account_data_size,
180 )
181 }
182
183 fn would_fit_internal<'a>(
184 &self,
185 write_lock_accounts: impl Iterator<Item = &'a Pubkey>,
186 cost: u64,
187 is_vote: bool,
188 account_data_size: u64,
189 ) -> Result<(), CostTrackerError> {
190 let vote_cost = if is_vote { cost } else { 0 };
191
192 if self.block_cost.saturating_add(cost) > self.block_cost_limit {
194 return Err(CostTrackerError::WouldExceedBlockMaxLimit);
195 }
196
197 if self.vote_cost.saturating_add(vote_cost) > self.vote_cost_limit {
199 return Err(CostTrackerError::WouldExceedVoteMaxLimit);
200 }
201
202 if cost > self.account_cost_limit {
204 return Err(CostTrackerError::WouldExceedAccountMaxLimit);
205 }
206
207 let account_data_size = self.account_data_size.saturating_add(account_data_size);
210 if let Some(account_data_size_limit) = self.account_data_size_limit {
211 if account_data_size > account_data_size_limit {
212 return Err(CostTrackerError::WouldExceedAccountDataTotalLimit);
213 }
214 }
215
216 if account_data_size > MAX_ACCOUNT_DATA_BLOCK_LEN {
217 return Err(CostTrackerError::WouldExceedAccountDataBlockLimit);
218 }
219
220 for account_key in write_lock_accounts {
222 match self.cost_by_writable_accounts.get(account_key) {
223 Some(chained_cost) => {
224 if chained_cost.saturating_add(cost) > self.account_cost_limit {
225 return Err(CostTrackerError::WouldExceedAccountMaxLimit);
226 } else {
227 continue;
228 }
229 }
230 None => continue,
231 }
232 }
233
234 Ok(())
235 }
236
237 fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) {
238 self.add_transaction_cost_internal(
239 tx_cost.writable_accounts.iter(),
240 tx_cost.sum(),
241 tx_cost.is_simple_vote,
242 tx_cost.account_data_size,
243 )
244 }
245
246 fn add_transaction_cost_internal<'a>(
247 &mut self,
248 write_lock_accounts: impl Iterator<Item = &'a Pubkey>,
249 cost: u64,
250 is_vote: bool,
251 account_data_size: u64,
252 ) {
253 self.add_transaction_execution_cost_internal(write_lock_accounts, is_vote, cost);
254 saturating_add_assign!(self.account_data_size, account_data_size);
255 saturating_add_assign!(self.transaction_count, 1);
256 }
257
258 fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) {
259 let cost = tx_cost.sum();
260 self.sub_transaction_execution_cost(tx_cost, cost);
261 self.account_data_size = self
262 .account_data_size
263 .saturating_sub(tx_cost.account_data_size);
264 self.transaction_count = self.transaction_count.saturating_sub(1);
265 }
266
267 fn add_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
269 self.add_transaction_execution_cost_internal(
270 tx_cost.writable_accounts.iter(),
271 tx_cost.is_simple_vote,
272 adjustment,
273 )
274 }
275
276 fn add_transaction_execution_cost_internal<'a>(
277 &mut self,
278 write_lock_accounts: impl Iterator<Item = &'a Pubkey>,
279 is_vote: bool,
280 adjustment: u64,
281 ) {
282 for account_key in write_lock_accounts {
283 let account_cost = self
284 .cost_by_writable_accounts
285 .entry(*account_key)
286 .or_insert(0);
287 *account_cost = account_cost.saturating_add(adjustment);
288 }
289 self.block_cost = self.block_cost.saturating_add(adjustment);
290 if is_vote {
291 self.vote_cost = self.vote_cost.saturating_add(adjustment);
292 }
293 }
294
295 fn sub_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
297 for account_key in tx_cost.writable_accounts.iter() {
298 let account_cost = self
299 .cost_by_writable_accounts
300 .entry(*account_key)
301 .or_insert(0);
302 *account_cost = account_cost.saturating_sub(adjustment);
303 }
304 self.block_cost = self.block_cost.saturating_sub(adjustment);
305 if tx_cost.is_simple_vote {
306 self.vote_cost = self.vote_cost.saturating_sub(adjustment);
307 }
308 }
309
310 fn number_of_accounts(&self) -> usize {
312 self.cost_by_writable_accounts
313 .values()
314 .filter(|units| **units > 0)
315 .count()
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use {
322 super::*,
323 crate::{
324 bank::Bank,
325 genesis_utils::{create_genesis_config, GenesisConfigInfo},
326 },
327 solana_sdk::{
328 hash::Hash,
329 signature::{Keypair, Signer},
330 system_transaction,
331 transaction::{
332 MessageHash, SanitizedTransaction, SimpleAddressLoader, VersionedTransaction,
333 },
334 },
335 solana_vote_program::vote_transaction,
336 std::{cmp, sync::Arc},
337 };
338
339 impl CostTracker {
340 fn new(
341 account_cost_limit: u64,
342 block_cost_limit: u64,
343 vote_cost_limit: u64,
344 account_data_size_limit: Option<u64>,
345 ) -> Self {
346 assert!(account_cost_limit <= block_cost_limit);
347 assert!(vote_cost_limit <= block_cost_limit);
348 Self {
349 account_cost_limit,
350 block_cost_limit,
351 vote_cost_limit,
352 account_data_size_limit,
353 ..Self::default()
354 }
355 }
356 }
357
358 fn test_setup() -> (Keypair, Hash) {
359 solana_logger::setup();
360 let GenesisConfigInfo {
361 genesis_config,
362 mint_keypair,
363 ..
364 } = create_genesis_config(10);
365 let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
366 let start_hash = bank.last_blockhash();
367 (mint_keypair, start_hash)
368 }
369
370 fn build_simple_transaction(
371 mint_keypair: &Keypair,
372 start_hash: &Hash,
373 ) -> (SanitizedTransaction, TransactionCost) {
374 let keypair = Keypair::new();
375 let simple_transaction = SanitizedTransaction::from_transaction_for_tests(
376 system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash),
377 );
378 let mut tx_cost = TransactionCost::new_with_capacity(1);
379 tx_cost.bpf_execution_cost = 5;
380 tx_cost.writable_accounts.push(mint_keypair.pubkey());
381
382 (simple_transaction, tx_cost)
383 }
384
385 fn build_simple_vote_transaction(
386 mint_keypair: &Keypair,
387 start_hash: &Hash,
388 ) -> (SanitizedTransaction, TransactionCost) {
389 let keypair = Keypair::new();
390 let transaction = vote_transaction::new_vote_transaction(
391 vec![42],
392 Hash::default(),
393 *start_hash,
394 mint_keypair,
395 &keypair,
396 &keypair,
397 None,
398 );
399 let vote_transaction = SanitizedTransaction::try_create(
400 VersionedTransaction::from(transaction),
401 MessageHash::Compute,
402 Some(true),
403 SimpleAddressLoader::Disabled,
404 true, )
406 .unwrap();
407 let mut tx_cost = TransactionCost::new_with_capacity(1);
408 tx_cost.bpf_execution_cost = 10;
409 tx_cost.writable_accounts.push(mint_keypair.pubkey());
410 tx_cost.is_simple_vote = true;
411
412 (vote_transaction, tx_cost)
413 }
414
415 #[test]
416 fn test_cost_tracker_initialization() {
417 let testee = CostTracker::new(10, 11, 8, None);
418 assert_eq!(10, testee.account_cost_limit);
419 assert_eq!(11, testee.block_cost_limit);
420 assert_eq!(8, testee.vote_cost_limit);
421 assert_eq!(0, testee.cost_by_writable_accounts.len());
422 assert_eq!(0, testee.block_cost);
423 }
424
425 #[test]
426 fn test_cost_tracker_ok_add_one() {
427 let (mint_keypair, start_hash) = test_setup();
428 let (_tx, tx_cost) = build_simple_transaction(&mint_keypair, &start_hash);
429 let cost = tx_cost.sum();
430
431 let mut testee = CostTracker::new(cost, cost, cost, None);
433 assert!(testee.would_fit(&tx_cost).is_ok());
434 testee.add_transaction_cost(&tx_cost);
435 assert_eq!(cost, testee.block_cost);
436 assert_eq!(0, testee.vote_cost);
437 let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
438 assert_eq!(cost, costliest_account_cost);
439 }
440
441 #[test]
442 fn test_cost_tracker_ok_add_one_vote() {
443 let (mint_keypair, start_hash) = test_setup();
444 let (_tx, tx_cost) = build_simple_vote_transaction(&mint_keypair, &start_hash);
445 let cost = tx_cost.sum();
446
447 let mut testee = CostTracker::new(cost, cost, cost, None);
449 assert!(testee.would_fit(&tx_cost).is_ok());
450 testee.add_transaction_cost(&tx_cost);
451 assert_eq!(cost, testee.block_cost);
452 assert_eq!(cost, testee.vote_cost);
453 let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
454 assert_eq!(cost, costliest_account_cost);
455 }
456
457 #[test]
458 fn test_cost_tracker_add_data() {
459 let (mint_keypair, start_hash) = test_setup();
460 let (_tx, mut tx_cost) = build_simple_transaction(&mint_keypair, &start_hash);
461 tx_cost.account_data_size = 1;
462 let cost = tx_cost.sum();
463
464 let mut testee = CostTracker::new(cost, cost, cost, None);
466 assert!(testee.would_fit(&tx_cost).is_ok());
467 let old = testee.account_data_size;
468 testee.add_transaction_cost(&tx_cost);
469 assert_eq!(old + 1, testee.account_data_size);
470 }
471
472 #[test]
473 fn test_cost_tracker_ok_add_two_same_accounts() {
474 let (mint_keypair, start_hash) = test_setup();
475 let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
477 let cost1 = tx_cost1.sum();
478 let (_tx2, tx_cost2) = build_simple_transaction(&mint_keypair, &start_hash);
479 let cost2 = tx_cost2.sum();
480
481 let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2, cost1 + cost2, None);
483 {
484 assert!(testee.would_fit(&tx_cost1).is_ok());
485 testee.add_transaction_cost(&tx_cost1);
486 }
487 {
488 assert!(testee.would_fit(&tx_cost2).is_ok());
489 testee.add_transaction_cost(&tx_cost2);
490 }
491 assert_eq!(cost1 + cost2, testee.block_cost);
492 assert_eq!(1, testee.cost_by_writable_accounts.len());
493 let (_ccostliest_account, costliest_account_cost) = testee.find_costliest_account();
494 assert_eq!(cost1 + cost2, costliest_account_cost);
495 }
496
497 #[test]
498 fn test_cost_tracker_ok_add_two_diff_accounts() {
499 let (mint_keypair, start_hash) = test_setup();
500 let second_account = Keypair::new();
502 let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
503 let cost1 = tx_cost1.sum();
504 let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash);
505 let cost2 = tx_cost2.sum();
506
507 let mut testee =
509 CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2, cost1 + cost2, None);
510 {
511 assert!(testee.would_fit(&tx_cost1).is_ok());
512 testee.add_transaction_cost(&tx_cost1);
513 }
514 {
515 assert!(testee.would_fit(&tx_cost2).is_ok());
516 testee.add_transaction_cost(&tx_cost2);
517 }
518 assert_eq!(cost1 + cost2, testee.block_cost);
519 assert_eq!(2, testee.cost_by_writable_accounts.len());
520 let (_ccostliest_account, costliest_account_cost) = testee.find_costliest_account();
521 assert_eq!(std::cmp::max(cost1, cost2), costliest_account_cost);
522 }
523
524 #[test]
525 fn test_cost_tracker_chain_reach_limit() {
526 let (mint_keypair, start_hash) = test_setup();
527 let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
529 let cost1 = tx_cost1.sum();
530 let (_tx2, tx_cost2) = build_simple_transaction(&mint_keypair, &start_hash);
531 let cost2 = tx_cost2.sum();
532
533 let mut testee =
535 CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2, cost1 + cost2, None);
536 {
538 assert!(testee.would_fit(&tx_cost1).is_ok());
539 testee.add_transaction_cost(&tx_cost1);
540 }
541 {
543 assert!(testee.would_fit(&tx_cost2).is_err());
544 }
545 }
546
547 #[test]
548 fn test_cost_tracker_reach_limit() {
549 let (mint_keypair, start_hash) = test_setup();
550 let second_account = Keypair::new();
552 let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
553 let cost1 = tx_cost1.sum();
554 let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash);
555 let cost2 = tx_cost2.sum();
556
557 let mut testee = CostTracker::new(
559 cmp::max(cost1, cost2),
560 cost1 + cost2 - 1,
561 cost1 + cost2 - 1,
562 None,
563 );
564 {
566 assert!(testee.would_fit(&tx_cost1).is_ok());
567 testee.add_transaction_cost(&tx_cost1);
568 }
569 {
571 assert!(testee.would_fit(&tx_cost2).is_err());
572 }
573 }
574
575 #[test]
576 fn test_cost_tracker_reach_vote_limit() {
577 let (mint_keypair, start_hash) = test_setup();
578 let second_account = Keypair::new();
580 let (_tx1, tx_cost1) = build_simple_vote_transaction(&mint_keypair, &start_hash);
581 let (_tx2, tx_cost2) = build_simple_vote_transaction(&second_account, &start_hash);
582 let cost1 = tx_cost1.sum();
583 let cost2 = tx_cost2.sum();
584
585 let mut testee = CostTracker::new(
587 cmp::max(cost1, cost2),
588 cost1 + cost2,
589 cost1 + cost2 - 1,
590 None,
591 );
592 {
594 assert!(testee.would_fit(&tx_cost1).is_ok());
595 testee.add_transaction_cost(&tx_cost1);
596 }
597 {
599 assert!(testee.would_fit(&tx_cost2).is_err());
600 }
601 {
603 let third_account = Keypair::new();
604 let (_tx3, tx_cost3) = build_simple_transaction(&third_account, &start_hash);
605 assert!(testee.would_fit(&tx_cost3).is_ok());
606 }
607 }
608
609 #[test]
610 fn test_cost_tracker_reach_data_block_limit() {
611 let (mint_keypair, start_hash) = test_setup();
612 let second_account = Keypair::new();
614 let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
615 let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash);
616 tx_cost1.account_data_size = MAX_ACCOUNT_DATA_BLOCK_LEN;
617 tx_cost2.account_data_size = MAX_ACCOUNT_DATA_BLOCK_LEN + 1;
618 let cost1 = tx_cost1.sum();
619 let cost2 = tx_cost2.sum();
620
621 let testee = CostTracker::new(
623 cmp::max(cost1, cost2),
624 cost1 + cost2 - 1,
625 cost1 + cost2 - 1,
626 None,
627 );
628 assert!(testee.would_fit(&tx_cost1).is_ok());
629 assert_eq!(
631 testee.would_fit(&tx_cost2),
632 Err(CostTrackerError::WouldExceedAccountDataBlockLimit),
633 );
634 }
635
636 #[test]
637 fn test_cost_tracker_reach_data_total_limit() {
638 let (mint_keypair, start_hash) = test_setup();
639 let second_account = Keypair::new();
641 let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
642 let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash);
643 let remaining_account_data_size = 1234;
644 tx_cost1.account_data_size = remaining_account_data_size;
645 tx_cost2.account_data_size = remaining_account_data_size + 1;
646 let cost1 = tx_cost1.sum();
647 let cost2 = tx_cost2.sum();
648
649 let testee = CostTracker::new(
651 cmp::max(cost1, cost2),
652 cost1 + cost2 - 1,
653 cost1 + cost2 - 1,
654 Some(remaining_account_data_size),
655 );
656 assert!(testee.would_fit(&tx_cost1).is_ok());
657 assert_eq!(
659 testee.would_fit(&tx_cost2),
660 Err(CostTrackerError::WouldExceedAccountDataTotalLimit),
661 );
662 }
663
664 #[test]
665 fn test_cost_tracker_remove() {
666 let (mint_keypair, start_hash) = test_setup();
667 let second_account = Keypair::new();
669 let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
670 let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash);
671 let cost1 = tx_cost1.sum();
672 let cost2 = tx_cost2.sum();
673 let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2, cost1 + cost2, None);
675
676 assert!(testee.try_add(&tx_cost1).is_ok());
677 assert!(testee.try_add(&tx_cost2).is_ok());
678 assert_eq!(cost1 + cost2, testee.block_cost);
679
680 testee.remove(&tx_cost1);
682 assert_eq!(cost2, testee.block_cost);
683
684 assert!(testee.try_add(&tx_cost1).is_ok());
686 assert_eq!(cost1 + cost2, testee.block_cost);
687
688 assert!(testee.try_add(&tx_cost1).is_err());
690 }
691
692 #[test]
693 fn test_cost_tracker_try_add_is_atomic() {
694 let acct1 = Pubkey::new_unique();
695 let acct2 = Pubkey::new_unique();
696 let acct3 = Pubkey::new_unique();
697 let cost = 100;
698 let account_max = cost * 2;
699 let block_max = account_max * 3; let mut testee = CostTracker::new(account_max, block_max, block_max, None);
702
703 {
709 let tx_cost = TransactionCost {
710 writable_accounts: vec![acct1, acct2, acct3],
711 bpf_execution_cost: cost,
712 ..TransactionCost::default()
713 };
714 assert!(testee.try_add(&tx_cost).is_ok());
715 let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
716 assert_eq!(cost, testee.block_cost);
717 assert_eq!(3, testee.cost_by_writable_accounts.len());
718 assert_eq!(cost, costliest_account_cost);
719 }
720
721 {
727 let tx_cost = TransactionCost {
728 writable_accounts: vec![acct2],
729 bpf_execution_cost: cost,
730 ..TransactionCost::default()
731 };
732 assert!(testee.try_add(&tx_cost).is_ok());
733 let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
734 assert_eq!(cost * 2, testee.block_cost);
735 assert_eq!(3, testee.cost_by_writable_accounts.len());
736 assert_eq!(cost * 2, costliest_account_cost);
737 assert_eq!(acct2, costliest_account);
738 }
739
740 {
747 let tx_cost = TransactionCost {
748 writable_accounts: vec![acct1, acct2],
749 bpf_execution_cost: cost,
750 ..TransactionCost::default()
751 };
752 assert!(testee.try_add(&tx_cost).is_err());
753 let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
754 assert_eq!(cost * 2, testee.block_cost);
755 assert_eq!(3, testee.cost_by_writable_accounts.len());
756 assert_eq!(cost * 2, costliest_account_cost);
757 assert_eq!(acct2, costliest_account);
758 }
759 }
760
761 #[test]
762 fn test_adjust_transaction_execution_cost() {
763 let acct1 = Pubkey::new_unique();
764 let acct2 = Pubkey::new_unique();
765 let acct3 = Pubkey::new_unique();
766 let cost = 100;
767 let account_max = cost * 2;
768 let block_max = account_max * 3; let mut testee = CostTracker::new(account_max, block_max, block_max, None);
771 let tx_cost = TransactionCost {
772 writable_accounts: vec![acct1, acct2, acct3],
773 bpf_execution_cost: cost,
774 ..TransactionCost::default()
775 };
776 let mut expected_block_cost = tx_cost.sum();
777 let expected_tx_count = 1;
778 assert!(testee.try_add(&tx_cost).is_ok());
779 assert_eq!(expected_block_cost, testee.block_cost());
780 assert_eq!(expected_tx_count, testee.transaction_count());
781 testee
782 .cost_by_writable_accounts
783 .iter()
784 .for_each(|(_key, units)| {
785 assert_eq!(expected_block_cost, *units);
786 });
787
788 {
790 let adjustment = 50u64;
791 testee.add_transaction_execution_cost(&tx_cost, adjustment);
792 expected_block_cost += 50;
793 assert_eq!(expected_block_cost, testee.block_cost());
794 assert_eq!(expected_tx_count, testee.transaction_count());
795 testee
796 .cost_by_writable_accounts
797 .iter()
798 .for_each(|(_key, units)| {
799 assert_eq!(expected_block_cost, *units);
800 });
801 }
802
803 {
805 let adjustment = 50u64;
806 testee.sub_transaction_execution_cost(&tx_cost, adjustment);
807 expected_block_cost -= 50;
808 assert_eq!(expected_block_cost, testee.block_cost());
809 assert_eq!(expected_tx_count, testee.transaction_count());
810 testee
811 .cost_by_writable_accounts
812 .iter()
813 .for_each(|(_key, units)| {
814 assert_eq!(expected_block_cost, *units);
815 });
816 }
817
818 {
820 testee.add_transaction_execution_cost(&tx_cost, u64::MAX);
821 assert_eq!(u64::MAX, testee.block_cost());
823 assert_eq!(expected_tx_count, testee.transaction_count());
824 testee
825 .cost_by_writable_accounts
826 .iter()
827 .for_each(|(_key, units)| {
828 assert_eq!(u64::MAX, *units);
829 });
830 }
831
832 {
834 testee.sub_transaction_execution_cost(&tx_cost, u64::MAX);
835 assert_eq!(u64::MIN, testee.block_cost());
837 assert_eq!(expected_tx_count, testee.transaction_count());
838 testee
839 .cost_by_writable_accounts
840 .iter()
841 .for_each(|(_key, units)| {
842 assert_eq!(u64::MIN, *units);
843 });
844 assert_eq!(0, testee.number_of_accounts());
847 assert_eq!(3, testee.cost_by_writable_accounts.len());
848 }
849 }
850
851 #[test]
852 fn test_update_execution_cost() {
853 let acct1 = Pubkey::new_unique();
854 let acct2 = Pubkey::new_unique();
855 let acct3 = Pubkey::new_unique();
856 let cost = 100;
857
858 let tx_cost = TransactionCost {
859 writable_accounts: vec![acct1, acct2, acct3],
860 bpf_execution_cost: cost,
861 ..TransactionCost::default()
862 };
863
864 let mut cost_tracker = CostTracker::default();
865
866 assert!(cost_tracker.try_add(&tx_cost).is_ok());
868 let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
869 assert_eq!(cost, cost_tracker.block_cost);
870 assert_eq!(cost, costliest_account_cost);
871 assert_eq!(1, cost_tracker.transaction_count);
872
873 let mut expected_cost = cost;
875 cost_tracker.update_execution_cost(&tx_cost, cost);
876 let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
877 assert_eq!(expected_cost, cost_tracker.block_cost);
878 assert_eq!(expected_cost, costliest_account_cost);
879 assert_eq!(1, cost_tracker.transaction_count);
880
881 let reduced_units = 3;
883 expected_cost -= reduced_units;
884 cost_tracker.update_execution_cost(&tx_cost, cost - reduced_units);
885 let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
886 assert_eq!(expected_cost, cost_tracker.block_cost);
887 assert_eq!(expected_cost, costliest_account_cost);
888 assert_eq!(1, cost_tracker.transaction_count);
889
890 let increased_units = 1;
892 expected_cost += increased_units;
893 cost_tracker.update_execution_cost(&tx_cost, cost + increased_units);
894 let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
895 assert_eq!(expected_cost, cost_tracker.block_cost);
896 assert_eq!(expected_cost, costliest_account_cost);
897 assert_eq!(1, cost_tracker.transaction_count);
898 }
899
900 #[test]
901 fn test_remove_transaction_cost() {
902 let mut cost_tracker = CostTracker::default();
903
904 let cost = 100u64;
905 let tx_cost = TransactionCost {
906 writable_accounts: vec![Pubkey::new_unique()],
907 bpf_execution_cost: cost,
908 ..TransactionCost::default()
909 };
910
911 cost_tracker.add_transaction_cost(&tx_cost);
912 assert_eq!(1, cost_tracker.transaction_count);
914 assert_eq!(1, cost_tracker.number_of_accounts());
915 assert_eq!(cost, cost_tracker.block_cost);
916 assert_eq!(0, cost_tracker.vote_cost);
917 assert_eq!(0, cost_tracker.account_data_size);
918
919 cost_tracker.remove_transaction_cost(&tx_cost);
920 assert_eq!(0, cost_tracker.transaction_count);
922 assert_eq!(0, cost_tracker.number_of_accounts());
923 assert_eq!(0, cost_tracker.block_cost);
924 assert_eq!(0, cost_tracker.vote_cost);
925 assert_eq!(0, cost_tracker.account_data_size);
926 }
927}