Skip to content

Commit

Permalink
default compute units per instruction (backport #24899) (#24944)
Browse files Browse the repository at this point in the history
* default compute units per instruction (#24899)

(cherry picked from commit e070c5c)

# Conflicts:
#	docs/src/developing/programming-model/runtime.md
#	sdk/src/feature_set.rs

* fix merge

Co-authored-by: Jack May <[email protected]>
  • Loading branch information
mergify[bot] and jackcmay authored May 3, 2022
1 parent 78271ee commit ece28a6
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 50 deletions.
9 changes: 4 additions & 5 deletions docs/src/developing/programming-model/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,10 @@ For information on what the compute budget is and how it is applied see [Compute
Budget](#compute-budget).

With a transaction-wide compute budget the `max_units` cap is applied to the
entire transaction rather than to each instruction within the transaction. The
default number of maximum units remains at 200k which means the sum of the
compute units used by each instruction in the transaction must not exceed that
value. The number of maximum units allows is intentionally kept small to
facilitate optimized programs and form the bases for a minimum fee level.
entire transaction rather than to each instruction within the transaction. The
default number of maximum units allowed to each transaction is a default value
per instruction in the transaction.The default value per instruction matches the
existing per-instruction cap to avoid breaking existing client behavior.

There are a lot of uses cases that require more than 200k units
transaction-wide. To enable these uses cases transactions can include a
Expand Down
140 changes: 104 additions & 36 deletions program-runtime/src/compute_budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use solana_sdk::{
transaction::TransactionError,
};

const MAX_UNITS: u32 = 1_400_000;
pub const DEFAULT_UNITS: u32 = 200_000;
pub const MAX_UNITS: u32 = 1_400_000;
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;

#[cfg(RUSTC_WITH_SPECIALIZATION)]
Expand Down Expand Up @@ -65,19 +66,14 @@ pub struct ComputeBudget {

impl Default for ComputeBudget {
fn default() -> Self {
Self::new(true)
Self::new(MAX_UNITS)
}
}

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;
pub fn new(max_units: u32) -> Self {
ComputeBudget {
max_units,
max_units: max_units as u64,
log_64_units: 100,
create_program_address_units: 1500,
invoke_units: 1000,
Expand All @@ -103,35 +99,54 @@ impl ComputeBudget {
&mut self,
message: &SanitizedMessage,
requestable_heap_size: bool,
default_units_per_instruction: bool,
) -> Result<u64, TransactionError> {
let mut num_instructions = message.instructions().len();
let mut requested_additional_fee = 0;
let mut requested_units = None;

let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData);
// Compute budget instruction must be in the 1st 3 instructions (avoid
// nonce marker), otherwise ignored
for (program_id, instruction) in message.program_instructions_iter().take(3) {
for (i, (program_id, instruction)) in message.program_instructions_iter().enumerate() {
if compute_budget::check_id(program_id) {
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnits {
units,
additional_fee,
}) => {
self.max_units = units.min(MAX_UNITS) as u64;
requested_additional_fee = additional_fee as u64;
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if !requestable_heap_size
|| bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
{
return Err(error);
// don't include request instructions in default max calc
num_instructions = num_instructions.saturating_sub(1);

// Compute budget instruction must be in the 1st 3 instructions (avoid
// nonce marker), otherwise ignored
if i < 3 {
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnits {
units,
additional_fee,
}) => {
requested_units = Some(units as u64);
requested_additional_fee = additional_fee as u64;
}
self.heap_size = Some(bytes as usize);
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if !requestable_heap_size
|| bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
{
return Err(error);
}
self.heap_size = Some(bytes as usize);
}
_ => return Err(error),
}
_ => return Err(error),
}
}
}

self.max_units = if default_units_per_instruction {
requested_units
.or_else(|| Some(num_instructions.saturating_mul(DEFAULT_UNITS as usize) as u64))
} else {
requested_units
}
.unwrap_or(MAX_UNITS as u64)
.min(MAX_UNITS as u64);

Ok(requested_additional_fee)
}
}
Expand Down Expand Up @@ -160,7 +175,7 @@ mod tests {
Hash::default(),
));
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_message(&tx.message(), true);
let result = compute_budget.process_message(&tx.message(), true, true);
assert_eq!($expected_error, result);
assert_eq!(compute_budget, $expected_budget);
};
Expand All @@ -169,7 +184,14 @@ mod tests {
#[test]
fn test_process_mesage() {
// Units
test!(&[], Ok(0), ComputeBudget::default());
test!(
&[],
Ok(0),
ComputeBudget {
max_units: 0,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::request_units(1, 0),
Expand All @@ -187,7 +209,10 @@ mod tests {
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(0),
ComputeBudget::default()
ComputeBudget {
max_units: MAX_UNITS as u64,
..ComputeBudget::default()
}
);
test!(
&[
Expand All @@ -205,21 +230,42 @@ mod tests {
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1, 0), // ignored
],
Ok(0),
ComputeBudget::default()
ComputeBudget {
max_units: DEFAULT_UNITS as u64 * 3,
..ComputeBudget::default()
}
);

// Additional fee
test!(
&[ComputeBudgetInstruction::request_units(1, 42),],
Ok(42),
ComputeBudget {
max_units: 1,
..ComputeBudget::default()
}
);

// HeapFrame
test!(&[], Ok(0), ComputeBudget::default());
test!(
&[],
Ok(0),
ComputeBudget {
max_units: 0,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(0),
ComputeBudget {
max_units: DEFAULT_UNITS as u64,
heap_size: Some(40 * 1024),
..ComputeBudget::default()
}
Expand Down Expand Up @@ -264,6 +310,7 @@ mod tests {
],
Ok(0),
ComputeBudget {
max_units: DEFAULT_UNITS as u64,
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
..ComputeBudget::default()
}
Expand All @@ -276,7 +323,28 @@ mod tests {
ComputeBudgetInstruction::request_heap_frame(1), // ignored
],
Ok(0),
ComputeBudget::default()
ComputeBudget {
max_units: DEFAULT_UNITS as u64 * 3,
..ComputeBudget::default()
}
);

test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(0),
ComputeBudget {
max_units: DEFAULT_UNITS as u64 * 7,
..ComputeBudget::default()
}
);

// Combined
Expand Down
9 changes: 5 additions & 4 deletions program-runtime/src/invoke_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,7 @@ pub fn mock_process_instruction(
mod tests {
use {
super::*,
crate::compute_budget,
serde::{Deserialize, Serialize},
solana_sdk::{
account::{ReadableAccount, WritableAccount},
Expand Down Expand Up @@ -1671,14 +1672,14 @@ mod tests {
feature_set.deactivate(&requestable_heap_size::id());
let mut invoke_context = InvokeContext::new_mock(&accounts, &[]);
invoke_context.feature_set = Arc::new(feature_set);
invoke_context.compute_budget = ComputeBudget::new(false);
invoke_context.compute_budget = ComputeBudget::new(compute_budget::DEFAULT_UNITS);

invoke_context
.push(&noop_message, &noop_message.instructions()[0], &[0], &[])
.unwrap();
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::new(false)
ComputeBudget::new(compute_budget::DEFAULT_UNITS)
);
invoke_context.pop();

Expand All @@ -1688,7 +1689,7 @@ mod tests {
let expected_compute_budget = ComputeBudget {
max_units: 500_000,
heap_size: Some(256_usize.saturating_mul(1024)),
..ComputeBudget::new(false)
..ComputeBudget::new(compute_budget::DEFAULT_UNITS)
};
assert_eq!(
*invoke_context.get_compute_budget(),
Expand All @@ -1701,7 +1702,7 @@ mod tests {
.unwrap();
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::new(false)
ComputeBudget::new(compute_budget::DEFAULT_UNITS)
);
invoke_context.pop();
}
Expand Down
16 changes: 11 additions & 5 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ use {
solana_measure::measure::Measure,
solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
solana_program_runtime::{
compute_budget::ComputeBudget,
compute_budget::{self, ComputeBudget},
invoke_context::{
BuiltinProgram, Executor, Executors, ProcessInstructionWithContext,
TransactionAccountRefCells, TransactionExecutor,
Expand All @@ -104,8 +104,8 @@ use {
epoch_schedule::EpochSchedule,
feature,
feature_set::{
self, disable_fee_calculator, nonce_must_be_writable, requestable_heap_size,
tx_wide_compute_cap, FeatureSet,
self, default_units_per_instruction, disable_fee_calculator, nonce_must_be_writable,
requestable_heap_size, tx_wide_compute_cap, FeatureSet,
},
fee::FeeStructure,
fee_calculator::{FeeCalculator, FeeRateGovernor},
Expand Down Expand Up @@ -4134,15 +4134,21 @@ impl Bank {
);

let tx_wide_compute_cap = feature_set.is_active(&tx_wide_compute_cap::id());
let compute_budget_max_units = if tx_wide_compute_cap {
compute_budget::MAX_UNITS
} else {
compute_budget::DEFAULT_UNITS
};
let mut compute_budget = self
.compute_budget
.unwrap_or_else(|| ComputeBudget::new(tx_wide_compute_cap));
.unwrap_or_else(|| ComputeBudget::new(compute_budget_max_units));
if tx_wide_compute_cap {
let mut compute_budget_process_transaction_time =
Measure::start("compute_budget_process_transaction_time");
let process_transaction_result = compute_budget.process_message(
tx.message(),
feature_set.is_active(&requestable_heap_size::id()),
feature_set.is_active(&default_units_per_instruction::id()),
);
compute_budget_process_transaction_time.stop();
saturating_add_assign!(
Expand Down Expand Up @@ -4366,7 +4372,7 @@ impl Bank {

let mut compute_budget = ComputeBudget::default();
let additional_fee = compute_budget
.process_message(message, false)
.process_message(message, false, false)
.unwrap_or_default();
let signature_fee = Self::get_num_signatures_in_message(message)
.saturating_mul(fee_structure.lamports_per_signature);
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/feature_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ pub mod drop_redundant_turbine_path {
solana_sdk::declare_id!("4Di3y24QFLt5QEUPZtbnjyfQKfm6ZMTfa6Dw1psfoMKU");
}

pub mod default_units_per_instruction {
solana_sdk::declare_id!("J2QdYx8crLbTVK8nur1jeLsmc3krDbfjoxoea2V1Uy5Q");
}

lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
Expand Down Expand Up @@ -383,6 +387,7 @@ lazy_static! {
(add_get_processed_sibling_instruction_syscall::id(), "add add_get_processed_sibling_instruction_syscall"),
(fixed_memcpy_nonoverlapping_check::id(), "use correct check for nonoverlapping regions in memcpy syscall"),
(drop_redundant_turbine_path::id(), "drop redundant turbine path"),
(default_units_per_instruction::id(), "Default max tx-wide compute units calculated per instruction"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()
Expand Down

0 comments on commit ece28a6

Please sign in to comment.