1use {
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 WouldExceedBlockMaxLimit,
21
22 WouldExceedVoteMaxLimit,
24
25 WouldExceedAccountMaxLimit,
27
28 WouldExceedAccountDataBlockLimit,
30
31 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#[derive(Debug, Default)]
53pub struct UpdatedCosts {
54 pub updated_block_cost: u64,
55 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 in_flight_transaction_count: Saturating<usize>,
78 secp256r1_instruction_signature_count: Saturating<u64>,
79}
80
81impl Default for CostTracker {
82 fn default() -> Self {
83 #![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 pub fn get_block_limit(&self) -> u64 {
136 self.block_cost_limit
137 }
138
139 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 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 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 return Err(CostTrackerError::WouldExceedBlockMaxLimit);
293 }
294
295 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let mut testee = CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2, cost1 + cost2);
598 {
600 assert!(testee.would_fit(&tx_cost1).is_ok());
601 testee.add_transaction_cost(&tx_cost1);
602 }
603 {
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 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 let mut testee =
623 CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1, cost1 + cost2 - 1);
624 {
626 assert!(testee.would_fit(&tx_cost1).is_ok());
627 testee.add_transaction_cost(&tx_cost1);
628 }
629 {
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 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 let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2, cost1 + cost2 - 1);
649 {
651 assert!(testee.would_fit(&tx_cost1).is_ok());
652 testee.add_transaction_cost(&tx_cost1);
653 }
654 {
656 assert!(testee.would_fit(&tx_cost2).is_err());
657 }
658 {
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 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 let testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1, cost1 + cost2 - 1);
691 assert!(testee.would_fit(&tx_cost1).is_ok());
692 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 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 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 testee.remove(&tx_cost1);
720 assert_eq!(cost2, testee.block_cost);
721
722 assert!(testee.try_add(&tx_cost1).is_ok());
724 assert_eq!(cost1 + cost2, testee.block_cost);
725
726 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; let mut testee = CostTracker::new(account_max, block_max, block_max);
740
741 {
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 {
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 {
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; 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 {
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 {
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 {
846 testee.add_transaction_execution_cost(&tx_cost, u64::MAX);
847 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 {
860 testee.sub_transaction_execution_cost(&tx_cost, u64::MAX);
861 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_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 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_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_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}