1use {
8 crate::{block_cost_limits::*, execute_cost_table::ExecuteCostTable},
9 log::*,
10 solana_sdk::{
11 instruction::CompiledInstruction, program_utils::limited_deserialize, pubkey::Pubkey,
12 system_instruction::SystemInstruction, system_program, transaction::SanitizedTransaction,
13 },
14};
15
16const MAX_WRITABLE_ACCOUNTS: usize = 256;
17
18#[derive(Debug)]
20pub struct TransactionCost {
21 pub writable_accounts: Vec<Pubkey>,
22 pub signature_cost: u64,
23 pub write_lock_cost: u64,
24 pub data_bytes_cost: u64,
25 pub builtins_execution_cost: u64,
26 pub bpf_execution_cost: u64,
27 pub account_data_size: u64,
28 pub is_simple_vote: bool,
29}
30
31impl Default for TransactionCost {
32 fn default() -> Self {
33 Self {
34 writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS),
35 signature_cost: 0u64,
36 write_lock_cost: 0u64,
37 data_bytes_cost: 0u64,
38 builtins_execution_cost: 0u64,
39 bpf_execution_cost: 0u64,
40 account_data_size: 0u64,
41 is_simple_vote: false,
42 }
43 }
44}
45
46impl TransactionCost {
47 pub fn new_with_capacity(capacity: usize) -> Self {
48 Self {
49 writable_accounts: Vec::with_capacity(capacity),
50 ..Self::default()
51 }
52 }
53
54 pub fn reset(&mut self) {
55 self.writable_accounts.clear();
56 self.signature_cost = 0;
57 self.write_lock_cost = 0;
58 self.data_bytes_cost = 0;
59 self.builtins_execution_cost = 0;
60 self.bpf_execution_cost = 0;
61 self.is_simple_vote = false;
62 }
63
64 pub fn sum(&self) -> u64 {
65 self.sum_without_bpf()
66 .saturating_add(self.bpf_execution_cost)
67 }
68
69 pub fn sum_without_bpf(&self) -> u64 {
70 self.signature_cost
71 .saturating_add(self.write_lock_cost)
72 .saturating_add(self.data_bytes_cost)
73 .saturating_add(self.builtins_execution_cost)
74 }
75}
76
77#[derive(Debug, Default)]
78pub struct CostModel {
79 instruction_execution_cost_table: ExecuteCostTable,
80}
81
82impl CostModel {
83 pub fn new() -> Self {
84 Self {
85 instruction_execution_cost_table: ExecuteCostTable::default(),
86 }
87 }
88
89 pub fn initialize_cost_table(&mut self, cost_table: &[(Pubkey, u64)]) {
90 cost_table
91 .iter()
92 .map(|(key, cost)| (key, cost))
93 .for_each(|(program_id, cost)| {
94 self.upsert_instruction_cost(program_id, *cost);
95 });
96 }
97
98 pub fn calculate_cost(&self, transaction: &SanitizedTransaction) -> TransactionCost {
99 let mut tx_cost = TransactionCost::new_with_capacity(MAX_WRITABLE_ACCOUNTS);
100
101 tx_cost.signature_cost = self.get_signature_cost(transaction);
102 self.get_write_lock_cost(&mut tx_cost, transaction);
103 self.get_transaction_cost(&mut tx_cost, transaction);
104 tx_cost.account_data_size = self.calculate_account_data_size(transaction);
105 tx_cost.is_simple_vote = transaction.is_simple_vote_transaction();
106
107 debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
108 tx_cost
109 }
110
111 pub fn upsert_instruction_cost(&mut self, program_key: &Pubkey, cost: u64) {
112 self.instruction_execution_cost_table
113 .upsert(program_key, cost);
114 }
115
116 pub fn find_instruction_cost(&self, program_key: &Pubkey) -> u64 {
117 match self.instruction_execution_cost_table.get_cost(program_key) {
118 Some(cost) => *cost,
119 None => {
120 let default_value = self
121 .instruction_execution_cost_table
122 .get_default_compute_unit_limit();
123 debug!(
124 "Program {:?} does not have aggregated cost, using default value {}",
125 program_key, default_value
126 );
127 default_value
128 }
129 }
130 }
131
132 fn get_signature_cost(&self, transaction: &SanitizedTransaction) -> u64 {
133 transaction.signatures().len() as u64 * SIGNATURE_COST
134 }
135
136 fn get_write_lock_cost(
137 &self,
138 tx_cost: &mut TransactionCost,
139 transaction: &SanitizedTransaction,
140 ) {
141 let message = transaction.message();
142 message
143 .account_keys()
144 .iter()
145 .enumerate()
146 .for_each(|(i, k)| {
147 let is_writable = message.is_writable(i);
148
149 if is_writable {
150 tx_cost.writable_accounts.push(*k);
151 tx_cost.write_lock_cost += WRITE_LOCK_UNITS;
152 }
153 });
154 }
155
156 fn get_transaction_cost(
157 &self,
158 tx_cost: &mut TransactionCost,
159 transaction: &SanitizedTransaction,
160 ) {
161 let mut builtin_costs = 0u64;
162 let mut bpf_costs = 0u64;
163 let mut data_bytes_len_total = 0u64;
164
165 for (program_id, instruction) in transaction.message().program_instructions_iter() {
166 if let Some(builtin_cost) = BUILT_IN_INSTRUCTION_COSTS.get(program_id) {
168 builtin_costs = builtin_costs.saturating_add(*builtin_cost);
169 } else {
170 let instruction_cost = self.find_instruction_cost(program_id);
171 trace!(
172 "instruction {:?} has cost of {}",
173 instruction,
174 instruction_cost
175 );
176 bpf_costs = bpf_costs.saturating_add(instruction_cost);
177 }
178 data_bytes_len_total =
179 data_bytes_len_total.saturating_add(instruction.data.len() as u64);
180 }
181 tx_cost.builtins_execution_cost = builtin_costs;
182 tx_cost.bpf_execution_cost = bpf_costs;
183 tx_cost.data_bytes_cost = data_bytes_len_total / DATA_BYTES_UNITS;
184 }
185
186 fn calculate_account_data_size_on_deserialized_system_instruction(
187 instruction: SystemInstruction,
188 ) -> u64 {
189 match instruction {
190 SystemInstruction::CreateAccount {
191 lamports: _lamports,
192 space,
193 owner: _owner,
194 } => space,
195 SystemInstruction::CreateAccountWithSeed {
196 base: _base,
197 seed: _seed,
198 lamports: _lamports,
199 space,
200 owner: _owner,
201 } => space,
202 SystemInstruction::Allocate { space } => space,
203 SystemInstruction::AllocateWithSeed {
204 base: _base,
205 seed: _seed,
206 space,
207 owner: _owner,
208 } => space,
209 _ => 0,
210 }
211 }
212
213 fn calculate_account_data_size_on_instruction(
214 program_id: &Pubkey,
215 instruction: &CompiledInstruction,
216 ) -> u64 {
217 if program_id == &system_program::id() {
218 if let Ok(instruction) = limited_deserialize(&instruction.data) {
219 return Self::calculate_account_data_size_on_deserialized_system_instruction(
220 instruction,
221 );
222 }
223 }
224 0
225 }
226
227 fn calculate_account_data_size(&self, transaction: &SanitizedTransaction) -> u64 {
230 transaction
231 .message()
232 .program_instructions_iter()
233 .map(|(program_id, instruction)| {
234 Self::calculate_account_data_size_on_instruction(program_id, instruction)
235 })
236 .sum()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use {
243 super::*,
244 crate::{
245 bank::Bank,
246 genesis_utils::{create_genesis_config, GenesisConfigInfo},
247 },
248 solana_sdk::{
249 bpf_loader,
250 hash::Hash,
251 instruction::CompiledInstruction,
252 message::Message,
253 signature::{Keypair, Signer},
254 system_instruction::{self},
255 system_program, system_transaction,
256 transaction::Transaction,
257 },
258 std::{
259 str::FromStr,
260 sync::{Arc, RwLock},
261 thread::{self, JoinHandle},
262 },
263 };
264
265 fn test_setup() -> (Keypair, Hash) {
266 solana_logger::setup();
267 let GenesisConfigInfo {
268 genesis_config,
269 mint_keypair,
270 ..
271 } = create_genesis_config(10);
272 let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
273 let start_hash = bank.last_blockhash();
274 (mint_keypair, start_hash)
275 }
276
277 #[test]
278 fn test_cost_model_instruction_cost() {
279 let mut testee = CostModel::default();
280
281 let known_key = Pubkey::from_str("known11111111111111111111111111111111111111").unwrap();
282 testee.upsert_instruction_cost(&known_key, 100);
283 assert_eq!(100, testee.find_instruction_cost(&known_key));
285
286 testee.upsert_instruction_cost(&bpf_loader::id(), 1999);
287 assert_eq!(1999, testee.find_instruction_cost(&bpf_loader::id()));
288
289 assert_eq!(
291 testee
292 .instruction_execution_cost_table
293 .get_default_compute_unit_limit(),
294 testee.find_instruction_cost(
295 &Pubkey::from_str("unknown111111111111111111111111111111111111").unwrap()
296 )
297 );
298 }
299
300 #[test]
301 fn test_cost_model_data_len_cost() {
302 let lamports = 0;
303 let owner = Pubkey::default();
304 let seed = String::default();
305 let space = 100;
306 let base = Pubkey::default();
307 for instruction in [
308 SystemInstruction::CreateAccount {
309 lamports,
310 space,
311 owner,
312 },
313 SystemInstruction::CreateAccountWithSeed {
314 base,
315 seed: seed.clone(),
316 lamports,
317 space,
318 owner,
319 },
320 SystemInstruction::Allocate { space },
321 SystemInstruction::AllocateWithSeed {
322 base,
323 seed,
324 space,
325 owner,
326 },
327 ] {
328 assert_eq!(
329 space,
330 CostModel::calculate_account_data_size_on_deserialized_system_instruction(
331 instruction
332 )
333 );
334 }
335 assert_eq!(
336 0,
337 CostModel::calculate_account_data_size_on_deserialized_system_instruction(
338 SystemInstruction::TransferWithSeed {
339 lamports,
340 from_seed: String::default(),
341 from_owner: Pubkey::default(),
342 }
343 )
344 );
345 }
346
347 #[test]
348 fn test_cost_model_simple_transaction() {
349 let (mint_keypair, start_hash) = test_setup();
350
351 let keypair = Keypair::new();
352 let simple_transaction = SanitizedTransaction::from_transaction_for_tests(
353 system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 2, start_hash),
354 );
355 debug!(
356 "system_transaction simple_transaction {:?}",
357 simple_transaction
358 );
359
360 let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
362 .get(&system_program::id())
363 .unwrap();
364
365 let testee = CostModel::default();
366 let mut tx_cost = TransactionCost::default();
367 testee.get_transaction_cost(&mut tx_cost, &simple_transaction);
368 assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
369 assert_eq!(0, tx_cost.bpf_execution_cost);
370 assert_eq!(0, tx_cost.data_bytes_cost);
371 }
372
373 #[test]
374 fn test_cost_model_transaction_many_transfer_instructions() {
375 let (mint_keypair, start_hash) = test_setup();
376
377 let key1 = solana_sdk::pubkey::new_rand();
378 let key2 = solana_sdk::pubkey::new_rand();
379 let instructions =
380 system_instruction::transfer_many(&mint_keypair.pubkey(), &[(key1, 1), (key2, 1)]);
381 let message = Message::new(&instructions, Some(&mint_keypair.pubkey()));
382 let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
383 &[&mint_keypair],
384 message,
385 start_hash,
386 ));
387 debug!("many transfer transaction {:?}", tx);
388
389 let program_cost = BUILT_IN_INSTRUCTION_COSTS
391 .get(&system_program::id())
392 .unwrap();
393 let expected_cost = program_cost * 2;
394
395 let testee = CostModel::default();
396 let mut tx_cost = TransactionCost::default();
397 testee.get_transaction_cost(&mut tx_cost, &tx);
398 assert_eq!(expected_cost, tx_cost.builtins_execution_cost);
399 assert_eq!(0, tx_cost.bpf_execution_cost);
400 assert_eq!(1, tx_cost.data_bytes_cost);
401 }
402
403 #[test]
404 fn test_cost_model_message_many_different_instructions() {
405 let (mint_keypair, start_hash) = test_setup();
406
407 let key1 = solana_sdk::pubkey::new_rand();
409 let key2 = solana_sdk::pubkey::new_rand();
410 let prog1 = solana_sdk::pubkey::new_rand();
411 let prog2 = solana_sdk::pubkey::new_rand();
412 let instructions = vec![
413 CompiledInstruction::new(3, &(), vec![0, 1]),
414 CompiledInstruction::new(4, &(), vec![0, 2]),
415 ];
416 let tx = SanitizedTransaction::from_transaction_for_tests(
417 Transaction::new_with_compiled_instructions(
418 &[&mint_keypair],
419 &[key1, key2],
420 start_hash,
421 vec![prog1, prog2],
422 instructions,
423 ),
424 );
425 debug!("many random transaction {:?}", tx);
426
427 let testee = CostModel::default();
428 let expected_cost = testee
429 .instruction_execution_cost_table
430 .get_default_compute_unit_limit()
431 * 2;
432 let mut tx_cost = TransactionCost::default();
433 testee.get_transaction_cost(&mut tx_cost, &tx);
434 assert_eq!(0, tx_cost.builtins_execution_cost);
435 assert_eq!(expected_cost, tx_cost.bpf_execution_cost);
436 assert_eq!(0, tx_cost.data_bytes_cost);
437 }
438
439 #[test]
440 fn test_cost_model_sort_message_accounts_by_type() {
441 let signer1 = Keypair::new();
443 let signer2 = Keypair::new();
444 let key1 = Pubkey::new_unique();
445 let key2 = Pubkey::new_unique();
446 let prog1 = Pubkey::new_unique();
447 let prog2 = Pubkey::new_unique();
448 let instructions = vec![
449 CompiledInstruction::new(4, &(), vec![0, 2]),
450 CompiledInstruction::new(5, &(), vec![1, 3]),
451 ];
452 let tx = SanitizedTransaction::from_transaction_for_tests(
453 Transaction::new_with_compiled_instructions(
454 &[&signer1, &signer2],
455 &[key1, key2],
456 Hash::new_unique(),
457 vec![prog1, prog2],
458 instructions,
459 ),
460 );
461
462 let cost_model = CostModel::default();
463 let tx_cost = cost_model.calculate_cost(&tx);
464 assert_eq!(2 + 2, tx_cost.writable_accounts.len());
465 assert_eq!(signer1.pubkey(), tx_cost.writable_accounts[0]);
466 assert_eq!(signer2.pubkey(), tx_cost.writable_accounts[1]);
467 assert_eq!(key1, tx_cost.writable_accounts[2]);
468 assert_eq!(key2, tx_cost.writable_accounts[3]);
469 }
470
471 #[test]
472 fn test_cost_model_insert_instruction_cost() {
473 let key1 = Pubkey::new_unique();
474 let cost1 = 100;
475
476 let mut cost_model = CostModel::default();
477 assert_eq!(
479 cost_model
480 .instruction_execution_cost_table
481 .get_default_compute_unit_limit(),
482 cost_model.find_instruction_cost(&key1)
483 );
484
485 cost_model.upsert_instruction_cost(&key1, cost1);
487
488 assert_eq!(cost1, cost_model.find_instruction_cost(&key1));
490 }
491
492 #[test]
493 fn test_cost_model_calculate_cost() {
494 let (mint_keypair, start_hash) = test_setup();
495 let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer(
496 &mint_keypair,
497 &Keypair::new().pubkey(),
498 2,
499 start_hash,
500 ));
501
502 let expected_account_cost = WRITE_LOCK_UNITS * 2;
503 let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
504 .get(&system_program::id())
505 .unwrap();
506
507 let cost_model = CostModel::default();
508 let tx_cost = cost_model.calculate_cost(&tx);
509 assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
510 assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
511 assert_eq!(2, tx_cost.writable_accounts.len());
512 }
513
514 #[test]
515 fn test_cost_model_update_instruction_cost() {
516 let key1 = Pubkey::new_unique();
517 let cost1 = 100;
518 let cost2 = 200;
519 let updated_cost = (cost1 + cost2) / 2;
520
521 let mut cost_model = CostModel::default();
522
523 cost_model.upsert_instruction_cost(&key1, cost1);
525 assert_eq!(cost1, cost_model.find_instruction_cost(&key1));
526
527 cost_model.upsert_instruction_cost(&key1, cost2);
529 assert_eq!(updated_cost, cost_model.find_instruction_cost(&key1));
530 }
531
532 #[test]
533 fn test_cost_model_can_be_shared_concurrently_with_rwlock() {
534 let (mint_keypair, start_hash) = test_setup();
535 let key1 = solana_sdk::pubkey::new_rand();
537 let key2 = solana_sdk::pubkey::new_rand();
538 let prog1 = solana_sdk::pubkey::new_rand();
539 let prog2 = solana_sdk::pubkey::new_rand();
540 let instructions = vec![
541 CompiledInstruction::new(3, &(), vec![0, 1]),
542 CompiledInstruction::new(4, &(), vec![0, 2]),
543 ];
544 let tx = Arc::new(SanitizedTransaction::from_transaction_for_tests(
545 Transaction::new_with_compiled_instructions(
546 &[&mint_keypair],
547 &[key1, key2],
548 start_hash,
549 vec![prog1, prog2],
550 instructions,
551 ),
552 ));
553
554 let number_threads = 10;
555 let expected_account_cost = WRITE_LOCK_UNITS * 3;
556 let cost1 = 100;
557 let cost2 = 200;
558 let cost_model: Arc<RwLock<CostModel>> = Arc::new(RwLock::new(CostModel::default()));
561
562 let thread_handlers: Vec<JoinHandle<()>> = (0..number_threads)
563 .map(|i| {
564 let cost_model = cost_model.clone();
565 let tx = tx.clone();
566
567 if i == 5 {
568 thread::spawn(move || {
569 let mut cost_model = cost_model.write().unwrap();
570 cost_model.upsert_instruction_cost(&prog1, cost1);
571 cost_model.upsert_instruction_cost(&prog2, cost2);
572 })
573 } else {
574 thread::spawn(move || {
575 let cost_model = cost_model.write().unwrap();
576 let tx_cost = cost_model.calculate_cost(&tx);
577 assert_eq!(3, tx_cost.writable_accounts.len());
578 assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
579 })
580 }
581 })
582 .collect();
583
584 for th in thread_handlers {
585 th.join().unwrap();
586 }
587 }
588
589 #[test]
590 fn test_initialize_cost_table() {
591 let cost_table = vec![
593 (Pubkey::new_unique(), 10),
594 (Pubkey::new_unique(), 20),
595 (Pubkey::new_unique(), 30),
596 ];
597
598 let mut cost_model = CostModel::default();
600 cost_model.initialize_cost_table(&cost_table);
601
602 for (id, cost) in cost_table.iter() {
604 assert_eq!(*cost, cost_model.find_instruction_cost(id));
605 }
606
607 assert!(cost_model
609 .instruction_execution_cost_table
610 .get_cost(&system_program::id())
611 .is_none());
612 assert!(cost_model
613 .instruction_execution_cost_table
614 .get_cost(&solana_vote_program::id())
615 .is_none());
616 }
617}