diff --git a/core/src/cost_update_service.rs b/core/src/cost_update_service.rs index ab2e3b46fb..35a82f8d87 100644 --- a/core/src/cost_update_service.rs +++ b/core/src/cost_update_service.rs @@ -150,15 +150,9 @@ mod tests { fn test_update_cost_model_with_empty_execute_timings() { let cost_model = Arc::new(RwLock::new(CostModel::default())); let mut empty_execute_timings = ExecuteTimings::default(); - CostUpdateService::update_cost_model(&cost_model, &mut empty_execute_timings); - assert_eq!( - 0, - cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .len() + CostUpdateService::update_cost_model(&cost_model, &mut empty_execute_timings), + 0 ); } @@ -188,14 +182,9 @@ mod tests { total_errored_units, }, ); - CostUpdateService::update_cost_model(&cost_model, &mut execute_timings); assert_eq!( - 1, - cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .len() + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 1 ); assert_eq!( Some(&expected_cost), @@ -225,14 +214,9 @@ mod tests { total_errored_units: 0, }, ); - CostUpdateService::update_cost_model(&cost_model, &mut execute_timings); assert_eq!( - 1, - cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .len() + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 1 ); assert_eq!( Some(&expected_cost), @@ -264,20 +248,47 @@ mod tests { total_errored_units: 0, }, ); - CostUpdateService::update_cost_model(&cost_model, &mut execute_timings); // If both the `errored_txs_compute_consumed` is empty and `count == 0`, then // nothing should be inserted into the cost model - assert!(cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .is_empty()); + assert_eq!( + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 0 + ); + } + + // set up current instruction cost to 100 + let current_program_cost = 100; + { + execute_timings.details.per_program_timings.insert( + program_key_1, + ProgramTiming { + accumulated_us: 1000, + accumulated_units: current_program_cost, + count: 1, + errored_txs_compute_consumed: vec![], + total_errored_units: 0, + }, + ); + assert_eq!( + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 1 + ); + assert_eq!( + Some(¤t_program_cost), + cost_model + .read() + .unwrap() + .get_instruction_cost_table() + .get(&program_key_1) + ); } // Test updating cost model with only erroring compute costs where the `cost_per_error` is // greater than the current instruction cost for the program. Should update with the // new erroring compute costs let cost_per_error = 1000; + // the expect cost is (previous_cost + new_cost)/2 = (100 + 1000)/2 = 550 + let expect_units = 550; { let errored_txs_compute_consumed = vec![cost_per_error; 3]; let total_errored_units = errored_txs_compute_consumed.iter().sum(); @@ -291,17 +302,12 @@ mod tests { total_errored_units, }, ); - CostUpdateService::update_cost_model(&cost_model, &mut execute_timings); assert_eq!( - 1, - cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .len() + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 1 ); assert_eq!( - Some(&cost_per_error), + Some(&expect_units), cost_model .read() .unwrap() @@ -313,7 +319,7 @@ mod tests { // Test updating cost model with only erroring compute costs where the error cost is // `smaller_cost_per_error`, less than the current instruction cost for the program. // The cost should not decrease for these new lesser errors - let smaller_cost_per_error = cost_per_error - 10; + let smaller_cost_per_error = expect_units - 10; { let errored_txs_compute_consumed = vec![smaller_cost_per_error; 3]; let total_errored_units = errored_txs_compute_consumed.iter().sum(); @@ -327,17 +333,12 @@ mod tests { total_errored_units, }, ); - CostUpdateService::update_cost_model(&cost_model, &mut execute_timings); assert_eq!( - 1, - cost_model - .read() - .unwrap() - .get_instruction_cost_table() - .len() + CostUpdateService::update_cost_model(&cost_model, &mut execute_timings), + 1 ); assert_eq!( - Some(&cost_per_error), + Some(&expect_units), cost_model .read() .unwrap() diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index 4edb658c5a..b3bba0d03e 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -73,13 +73,8 @@ impl Default for ComputeBudget { impl ComputeBudget { pub fn new(use_max_units_default: bool) -> Self { - let max_units = if use_max_units_default { - MAX_UNITS - } else { - 200_000 - } as u64; ComputeBudget { - max_units, + max_units: ComputeBudget::get_max_units(use_max_units_default), log_64_units: 100, create_program_address_units: 1500, invoke_units: 1000, @@ -102,6 +97,14 @@ impl ComputeBudget { } } + pub fn get_max_units(use_max_units_default: bool) -> u64 { + if use_max_units_default { + MAX_UNITS as u64 + } else { + 200_000 + } + } + pub fn process_message( &mut self, message: &SanitizedMessage, diff --git a/runtime/src/cost_model.rs b/runtime/src/cost_model.rs index 8934e7b7f2..66148e2483 100644 --- a/runtime/src/cost_model.rs +++ b/runtime/src/cost_model.rs @@ -113,9 +113,9 @@ impl CostModel { match self.instruction_execution_cost_table.get_cost(program_key) { Some(cost) => *cost, None => { - let default_value = self.instruction_execution_cost_table.get_mode(); + let default_value = self.instruction_execution_cost_table.get_default_units(); debug!( - "Program key {:?} does not have assigned cost, using mode {}", + "Instruction {:?} does not have aggregated cost, using default value {}", program_key, default_value ); default_value @@ -278,7 +278,7 @@ mod tests { // unknown program is assigned with default cost assert_eq!( - testee.instruction_execution_cost_table.get_mode(), + testee.instruction_execution_cost_table.get_default_units(), testee.find_instruction_cost( &Pubkey::from_str("unknown111111111111111111111111111111111111").unwrap() ) @@ -409,7 +409,7 @@ mod tests { let result = testee.get_transaction_cost(&tx); // expected cost for two random/unknown program is - let expected_cost = testee.instruction_execution_cost_table.get_mode() * 2; + let expected_cost = testee.instruction_execution_cost_table.get_default_units() * 2; assert_eq!(expected_cost, result); } @@ -453,7 +453,9 @@ mod tests { let mut cost_model = CostModel::default(); // Using default cost for unknown instruction assert_eq!( - cost_model.instruction_execution_cost_table.get_mode(), + cost_model + .instruction_execution_cost_table + .get_default_units(), cost_model.find_instruction_cost(&key1) ); diff --git a/runtime/src/execute_cost_table.rs b/runtime/src/execute_cost_table.rs index 8c85315234..6cb5f15cc0 100644 --- a/runtime/src/execute_cost_table.rs +++ b/runtime/src/execute_cost_table.rs @@ -3,8 +3,10 @@ /// unchecked. /// When its capacity limit is reached, it prunes old and less-used programs /// to make room for new ones. -use log::*; -use {solana_sdk::pubkey::Pubkey, std::collections::HashMap}; +use { + log::*, solana_program_runtime::compute_budget::ComputeBudget, solana_sdk::pubkey::Pubkey, + std::collections::HashMap, +}; // prune is rather expensive op, free up bulk space in each operation // would be more efficient. PRUNE_RATIO defines the after prune table @@ -45,19 +47,25 @@ impl ExecuteCostTable { self.table.len() } - // instead of assigning unknown program with a configured/hard-coded cost - // use average or mode function to make a educated guess. - pub fn get_average(&self) -> u64 { + // default program cost to max_units + pub fn get_default_units(&self) -> u64 { + let use_max_units_default = false; + ComputeBudget::get_max_units(use_max_units_default) + } + + // average cost of all recorded programs + pub fn get_average_units(&self) -> u64 { if self.table.is_empty() { - 0 + self.get_default_units() } else { self.table.iter().map(|(_, value)| value).sum::() / self.get_count() as u64 } } - pub fn get_mode(&self) -> u64 { + // the most frequently occurring program's cost + pub fn get_mode_units(&self) -> u64 { if self.occurrences.is_empty() { - 0 + self.get_default_units() } else { let key = self .occurrences @@ -71,8 +79,8 @@ impl ExecuteCostTable { } // returns None if program doesn't exist in table. In this case, - // client is advised to call `get_average()` or `get_mode()` to - // assign a 'default' value for new program. + // `get_default_units()`, `get_average_units()` or `get_mode_units()` + // can be used to assign a value to new program. pub fn get_cost(&self, key: &Pubkey) -> Option<&u64> { self.table.get(key) } @@ -219,23 +227,26 @@ mod tests { // insert one record testee.upsert(&key1, cost1); assert_eq!(1, testee.get_count()); - assert_eq!(cost1, testee.get_average()); - assert_eq!(cost1, testee.get_mode()); + assert_eq!(cost1, testee.get_average_units()); + assert_eq!(cost1, testee.get_mode_units()); assert_eq!(&cost1, testee.get_cost(&key1).unwrap()); // insert 2nd record testee.upsert(&key2, cost2); assert_eq!(2, testee.get_count()); - assert_eq!((cost1 + cost2) / 2_u64, testee.get_average()); - assert_eq!(cost2, testee.get_mode()); + assert_eq!((cost1 + cost2) / 2_u64, testee.get_average_units()); + assert_eq!(cost2, testee.get_mode_units()); assert_eq!(&cost1, testee.get_cost(&key1).unwrap()); assert_eq!(&cost2, testee.get_cost(&key2).unwrap()); // update 1st record testee.upsert(&key1, cost2); assert_eq!(2, testee.get_count()); - assert_eq!(((cost1 + cost2) / 2 + cost2) / 2, testee.get_average()); - assert_eq!((cost1 + cost2) / 2, testee.get_mode()); + assert_eq!( + ((cost1 + cost2) / 2 + cost2) / 2_u64, + testee.get_average_units() + ); + assert_eq!((cost1 + cost2) / 2, testee.get_mode_units()); assert_eq!(&((cost1 + cost2) / 2), testee.get_cost(&key1).unwrap()); assert_eq!(&cost2, testee.get_cost(&key2).unwrap()); } @@ -269,8 +280,8 @@ mod tests { // insert 3rd record, pushes out the oldest (eg 1st) record testee.upsert(&key3, cost3); assert_eq!(2, testee.get_count()); - assert_eq!((cost2 + cost3) / 2_u64, testee.get_average()); - assert_eq!(cost3, testee.get_mode()); + assert_eq!((cost2 + cost3) / 2_u64, testee.get_average_units()); + assert_eq!(cost3, testee.get_mode_units()); assert!(testee.get_cost(&key1).is_none()); assert_eq!(&cost2, testee.get_cost(&key2).unwrap()); assert_eq!(&cost3, testee.get_cost(&key3).unwrap()); @@ -279,8 +290,11 @@ mod tests { // add 4th record, pushes out 3rd key testee.upsert(&key2, cost1); testee.upsert(&key4, cost4); - assert_eq!(((cost1 + cost2) / 2 + cost4) / 2_u64, testee.get_average()); - assert_eq!((cost1 + cost2) / 2, testee.get_mode()); + assert_eq!( + ((cost1 + cost2) / 2 + cost4) / 2_u64, + testee.get_average_units() + ); + assert_eq!((cost1 + cost2) / 2, testee.get_mode_units()); assert_eq!(2, testee.get_count()); assert!(testee.get_cost(&key1).is_none()); assert_eq!(&((cost1 + cost2) / 2), testee.get_cost(&key2).unwrap());