Split compute budget instructions process from struct itself (#33513)

* Split compute budget instruction processing from ComputeBudget struct itself, allow compute_budget_instructions be processed elsewhere without having to instantiate ComputeBudget

* updated tests
This commit is contained in:
Tao Zhu 2023-10-19 11:10:42 -05:00 committed by GitHub
parent 4e5c545e23
commit c73bebe984
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 796 additions and 762 deletions

View File

@ -25,7 +25,7 @@ use {
itertools::Itertools,
log::*,
solana_program_runtime::{
compute_budget::{self, ComputeBudget},
compute_budget_processor::process_compute_budget_instructions,
loaded_programs::LoadedProgramsForTxBatch,
},
solana_sdk::{
@ -35,9 +35,8 @@ use {
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot},
feature_set::{
self, add_set_tx_loaded_accounts_data_size_instruction,
include_loaded_accounts_data_size_in_fee_calculation,
remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix,
self, include_loaded_accounts_data_size_in_fee_calculation,
remove_congestion_multiplier_from_fee_calculation,
simplify_writable_program_account_check, FeatureSet,
},
fee::FeeStructure,
@ -247,15 +246,16 @@ impl Accounts {
feature_set: &FeatureSet,
) -> Result<Option<NonZeroUsize>> {
if feature_set.is_active(&feature_set::cap_transaction_accounts_data_size::id()) {
let mut compute_budget =
ComputeBudget::new(compute_budget::MAX_COMPUTE_UNIT_LIMIT as u64);
let _process_transaction_result = compute_budget.process_instructions(
let compute_budget_limits = process_compute_budget_instructions(
tx.message().program_instructions_iter(),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()),
);
feature_set,
)
.unwrap_or_default();
// sanitize against setting size limit to zero
NonZeroUsize::new(compute_budget.loaded_accounts_data_size_limit).map_or(
NonZeroUsize::new(
usize::try_from(compute_budget_limits.loaded_accounts_bytes).unwrap_or_default(),
)
.map_or(
Err(TransactionError::InvalidLoadedAccountsDataSizeLimit),
|v| Ok(Some(v)),
)
@ -722,7 +722,7 @@ impl Accounts {
fee_structure.calculate_fee(
tx.message(),
lamports_per_signature,
&ComputeBudget::fee_budget_limits(tx.message().program_instructions_iter(), feature_set),
&process_compute_budget_instructions(tx.message().program_instructions_iter(), feature_set).unwrap_or_default().into(),
feature_set.is_active(&remove_congestion_multiplier_from_fee_calculation::id()),
feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()),
)
@ -1474,8 +1474,9 @@ mod tests {
transaction_results::{DurableNonceFee, TransactionExecutionDetails},
},
assert_matches::assert_matches,
solana_program_runtime::prioritization_fee::{
PrioritizationFeeDetails, PrioritizationFeeType,
solana_program_runtime::{
compute_budget_processor,
prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
},
solana_sdk::{
account::{AccountSharedData, WritableAccount},
@ -1751,13 +1752,15 @@ mod tests {
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&remove_deprecated_request_unit_ix::id());
feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id());
let message = SanitizedMessage::try_from(tx.message().clone()).unwrap();
let fee = FeeStructure::default().calculate_fee(
&message,
lamports_per_signature,
&ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set),
&process_compute_budget_instructions(message.program_instructions_iter(), &feature_set)
.unwrap_or_default()
.into(),
true,
false,
);
@ -4253,7 +4256,11 @@ mod tests {
let result_no_limit = Ok(None);
let result_default_limit = Ok(Some(
NonZeroUsize::new(compute_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES).unwrap(),
NonZeroUsize::new(
usize::try_from(compute_budget_processor::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES)
.unwrap(),
)
.unwrap(),
));
let result_requested_limit: Result<Option<NonZeroUsize>> =
Ok(Some(NonZeroUsize::new(99).unwrap()));
@ -4281,7 +4288,10 @@ mod tests {
// if tx doesn't set limit, then default limit (64MiB)
// if tx sets limit, then requested limit
// if tx sets limit to zero, then TransactionError::InvalidLoadedAccountsDataSizeLimit
feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0);
feature_set.activate(
&solana_sdk::feature_set::add_set_tx_loaded_accounts_data_size_instruction::id(),
0,
);
test(tx_not_set_limit, &feature_set, &result_default_limit);
test(tx_set_limit_99, &feature_set, &result_requested_limit);
test(tx_set_limit_0, &feature_set, &result_invalid_limit);
@ -4316,13 +4326,15 @@ mod tests {
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&remove_deprecated_request_unit_ix::id());
feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id());
let message = SanitizedMessage::try_from(tx.message().clone()).unwrap();
let fee = FeeStructure::default().calculate_fee(
&message,
lamports_per_signature,
&ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set),
&process_compute_budget_instructions(message.program_instructions_iter(), &feature_set)
.unwrap_or_default()
.into(),
true,
false,
);

View File

@ -8,17 +8,17 @@
use {
crate::{block_cost_limits::*, transaction_cost::*},
log::*,
solana_program_runtime::compute_budget::{
ComputeBudget, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT,
solana_program_runtime::{
compute_budget::DEFAULT_HEAP_COST,
compute_budget_processor::{
process_compute_budget_instructions, ComputeBudgetLimits,
DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT,
},
},
solana_sdk::{
borsh0_10::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
feature_set::{
add_set_tx_loaded_accounts_data_size_instruction,
include_loaded_accounts_data_size_in_fee_calculation,
remove_deprecated_request_unit_ix, FeatureSet,
},
feature_set::{include_loaded_accounts_data_size_in_fee_calculation, FeatureSet},
fee::FeeStructure,
instruction::CompiledInstruction,
program_utils::limited_deserialize,
@ -62,10 +62,12 @@ impl CostModel {
// to set limit, `compute_budget.loaded_accounts_data_size_limit` is set to default
// limit of 64MB; which will convert to (64M/32K)*8CU = 16_000 CUs
//
pub fn calculate_loaded_accounts_data_size_cost(compute_budget: &ComputeBudget) -> u64 {
pub fn calculate_loaded_accounts_data_size_cost(
compute_budget_limits: &ComputeBudgetLimits,
) -> u64 {
FeeStructure::calculate_memory_usage_cost(
compute_budget.loaded_accounts_data_size_limit,
compute_budget.heap_cost,
usize::try_from(compute_budget_limits.loaded_accounts_bytes).unwrap(),
DEFAULT_HEAP_COST,
)
}
@ -128,32 +130,28 @@ impl CostModel {
}
// calculate bpf cost based on compute budget instructions
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_instructions(
transaction.message().program_instructions_iter(),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()),
);
// if failed to process compute_budget instructions, the transaction will not be executed
// by `bank`, therefore it should be considered as no execution cost by cost model.
match result {
Ok(_) => {
match process_compute_budget_instructions(
transaction.message().program_instructions_iter(),
feature_set,
) {
Ok(compute_budget_limits) => {
// if tx contained user-space instructions and a more accurate estimate available correct it,
// where "user-space instructions" must be specifically checked by
// 'compute_unit_limit_is_set' flag, because compute_budget does not distinguish
// builtin and bpf instructions when calculating default compute-unit-limit. (see
// compute_budget.rs test `test_process_mixed_instructions_without_compute_budget`)
if bpf_costs > 0 && compute_unit_limit_is_set {
bpf_costs = compute_budget.compute_unit_limit
bpf_costs = u64::from(compute_budget_limits.compute_unit_limit);
}
if feature_set
.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id())
{
loaded_accounts_data_size_cost =
Self::calculate_loaded_accounts_data_size_cost(&compute_budget);
Self::calculate_loaded_accounts_data_size_cost(&compute_budget_limits);
}
}
Err(_) => {
@ -545,7 +543,8 @@ mod tests {
// default loaded_accounts_data_size_limit
const DEFAULT_PAGE_COST: u64 = 8;
let expected_loaded_accounts_data_size_cost =
solana_program_runtime::compute_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES as u64
solana_program_runtime::compute_budget_processor::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES
as u64
/ ACCOUNT_DATA_COST_PAGE_SIZE
* DEFAULT_PAGE_COST;
@ -663,36 +662,36 @@ mod tests {
#[allow(clippy::field_reassign_with_default)]
#[test]
fn test_calculate_loaded_accounts_data_size_cost() {
let mut compute_budget = ComputeBudget::default();
let mut compute_budget_limits = ComputeBudgetLimits::default();
// accounts data size are priced in block of 32K, ...
// ... requesting less than 32K should still be charged as one block
compute_budget.loaded_accounts_data_size_limit = 31_usize * 1024;
compute_budget_limits.loaded_accounts_bytes = 31 * 1024;
assert_eq!(
compute_budget.heap_cost,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
DEFAULT_HEAP_COST,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits)
);
// ... requesting exact 32K should be charged as one block
compute_budget.loaded_accounts_data_size_limit = 32_usize * 1024;
compute_budget_limits.loaded_accounts_bytes = 32 * 1024;
assert_eq!(
compute_budget.heap_cost,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
DEFAULT_HEAP_COST,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits)
);
// ... requesting slightly above 32K should be charged as 2 block
compute_budget.loaded_accounts_data_size_limit = 33_usize * 1024;
compute_budget_limits.loaded_accounts_bytes = 33 * 1024;
assert_eq!(
compute_budget.heap_cost * 2,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
DEFAULT_HEAP_COST * 2,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits)
);
// ... requesting exact 64K should be charged as 2 block
compute_budget.loaded_accounts_data_size_limit = 64_usize * 1024;
compute_budget_limits.loaded_accounts_bytes = 64 * 1024;
assert_eq!(
compute_budget.heap_cost * 2,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
DEFAULT_HEAP_COST * 2,
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits)
);
}

View File

@ -1,28 +1,11 @@
use {
crate::prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
crate::compute_budget_processor::{self, process_compute_budget_instructions},
solana_sdk::{
borsh0_10::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
feature_set::{
add_set_tx_loaded_accounts_data_size_instruction, remove_deprecated_request_unit_ix,
FeatureSet,
},
fee::FeeBudgetLimits,
instruction::{CompiledInstruction, InstructionError},
pubkey::Pubkey,
transaction::TransactionError,
feature_set::FeatureSet, instruction::CompiledInstruction, pubkey::Pubkey,
transaction::Result,
},
};
/// The total accounts data a transaction can load is limited to 64MiB to not break
/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction
pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: usize = 64 * 1024 * 1024;
pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget {
fn example() -> Self {
@ -31,6 +14,10 @@ impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget {
}
}
/// Roughly 0.5us/page, where page is 32K; given roughly 15CU/us, the
/// default heap page cost = 0.5 * 15 ~= 8CU/page
pub const DEFAULT_HEAP_COST: u64 = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ComputeBudget {
/// Number of compute units that a transaction or individual instruction is
@ -118,9 +105,6 @@ pub struct ComputeBudget {
pub alt_bn128_pairing_one_pair_cost_other: u64,
/// Big integer modular exponentiation cost
pub big_modular_exponentiation_cost: u64,
/// Maximum accounts data size, in bytes, that a transaction is allowed to load; The
/// value is capped by MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES to prevent overuse of memory.
pub loaded_accounts_data_size_limit: usize,
/// Coefficient `a` of the quadratic function which determines the number
/// of compute units consumed to call poseidon syscall for a given number
/// of inputs.
@ -143,7 +127,7 @@ pub struct ComputeBudget {
impl Default for ComputeBudget {
fn default() -> Self {
Self::new(MAX_COMPUTE_UNIT_LIMIT as u64)
Self::new(compute_budget_processor::MAX_COMPUTE_UNIT_LIMIT as u64)
}
}
@ -180,14 +164,13 @@ impl ComputeBudget {
curve25519_ristretto_msm_base_cost: 2303,
curve25519_ristretto_msm_incremental_cost: 788,
heap_size: u32::try_from(solana_sdk::entrypoint::HEAP_LENGTH).unwrap(),
heap_cost: 8,
heap_cost: DEFAULT_HEAP_COST,
mem_op_base_cost: 10,
alt_bn128_addition_cost: 334,
alt_bn128_multiplication_cost: 3_840,
alt_bn128_pairing_one_pair_cost_first: 36_364,
alt_bn128_pairing_one_pair_cost_other: 12_121,
big_modular_exponentiation_cost: 33,
loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
poseidon_cost_coefficient_a: 61,
poseidon_cost_coefficient_c: 542,
get_remaining_compute_units_cost: 100,
@ -198,127 +181,16 @@ impl ComputeBudget {
}
}
pub fn process_instructions<'a>(
&mut self,
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
support_request_units_deprecated: bool,
support_set_loaded_accounts_data_size_limit_ix: bool,
) -> Result<PrioritizationFeeDetails, TransactionError> {
let mut num_non_compute_budget_instructions: u32 = 0;
let mut updated_compute_unit_limit = None;
let mut requested_heap_size = None;
let mut prioritization_fee = None;
let mut updated_loaded_accounts_data_size_limit = None;
for (i, (program_id, instruction)) in instructions.enumerate() {
if compute_budget::check_id(program_id) {
let invalid_instruction_data_error = TransactionError::InstructionError(
i as u8,
InstructionError::InvalidInstructionData,
);
let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8);
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnitsDeprecated {
units: compute_unit_limit,
additional_fee,
}) if support_request_units_deprecated => {
if updated_compute_unit_limit.is_some() {
return Err(duplicate_instruction_error);
}
if prioritization_fee.is_some() {
return Err(duplicate_instruction_error);
}
updated_compute_unit_limit = Some(compute_unit_limit);
prioritization_fee =
Some(PrioritizationFeeType::Deprecated(additional_fee as u64));
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if requested_heap_size.is_some() {
return Err(duplicate_instruction_error);
}
requested_heap_size = Some((bytes, i as u8));
}
Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => {
if updated_compute_unit_limit.is_some() {
return Err(duplicate_instruction_error);
}
updated_compute_unit_limit = Some(compute_unit_limit);
}
Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => {
if prioritization_fee.is_some() {
return Err(duplicate_instruction_error);
}
prioritization_fee =
Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports));
}
Ok(ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(bytes))
if support_set_loaded_accounts_data_size_limit_ix =>
{
if updated_loaded_accounts_data_size_limit.is_some() {
return Err(duplicate_instruction_error);
}
updated_loaded_accounts_data_size_limit = Some(bytes as usize);
}
_ => return Err(invalid_instruction_data_error),
}
} else {
// only include non-request instructions in default max calc
num_non_compute_budget_instructions =
num_non_compute_budget_instructions.saturating_add(1);
}
}
if let Some((bytes, i)) = requested_heap_size {
if bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
{
return Err(TransactionError::InstructionError(
i,
InstructionError::InvalidInstructionData,
));
}
self.heap_size = bytes;
}
let compute_unit_limit = updated_compute_unit_limit
.unwrap_or_else(|| {
num_non_compute_budget_instructions
.saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT)
})
.min(MAX_COMPUTE_UNIT_LIMIT);
self.compute_unit_limit = u64::from(compute_unit_limit);
self.loaded_accounts_data_size_limit = updated_loaded_accounts_data_size_limit
.unwrap_or(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES)
.min(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES);
Ok(prioritization_fee
.map(|fee_type| PrioritizationFeeDetails::new(fee_type, self.compute_unit_limit))
.unwrap_or_default())
}
pub fn fee_budget_limits<'a>(
pub fn try_from_instructions<'a>(
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
feature_set: &FeatureSet,
) -> FeeBudgetLimits {
let mut compute_budget = Self::default();
let prioritization_fee_details = compute_budget
.process_instructions(
instructions,
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()),
)
.unwrap_or_default();
FeeBudgetLimits {
loaded_accounts_data_size_limit: compute_budget.loaded_accounts_data_size_limit,
heap_cost: compute_budget.heap_cost,
compute_unit_limit: compute_budget.compute_unit_limit,
prioritization_fee: prioritization_fee_details.get_fee(),
}
) -> Result<Self> {
let compute_budget_limits = process_compute_budget_instructions(instructions, feature_set)?;
Ok(ComputeBudget {
compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit),
heap_size: compute_budget_limits.updated_heap_bytes,
..ComputeBudget::default()
})
}
/// Returns cost of the Poseidon hash function for the given number of
@ -350,489 +222,3 @@ impl ComputeBudget {
Some(final_result)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_sdk::{
hash::Hash,
instruction::Instruction,
message::Message,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
system_instruction::{self},
transaction::{SanitizedTransaction, Transaction},
},
};
macro_rules! test {
( $instructions: expr, $expected_result: expr, $expected_budget: expr, $support_set_loaded_accounts_data_size_limit_ix: expr ) => {
let payer_keypair = Keypair::new();
let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
&[&payer_keypair],
Message::new($instructions, Some(&payer_keypair.pubkey())),
Hash::default(),
));
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_instructions(
tx.message().program_instructions_iter(),
false, /*not support request_units_deprecated*/
$support_set_loaded_accounts_data_size_limit_ix,
);
assert_eq!($expected_result, result);
assert_eq!(compute_budget, $expected_budget);
};
( $instructions: expr, $expected_result: expr, $expected_budget: expr) => {
test!($instructions, $expected_result, $expected_budget, false);
};
}
#[test]
fn test_process_instructions() {
// Units
test!(
&[],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 0,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 1,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(1),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 1,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1),
ComputeBudgetInstruction::set_compute_unit_price(42)
],
Ok(PrioritizationFeeDetails::new(
PrioritizationFeeType::ComputeUnitPrice(42),
1
)),
ComputeBudget {
compute_unit_limit: 1,
..ComputeBudget::default()
}
);
// HeapFrame
test!(
&[],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 0,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
heap_size: 40 * 1024,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(31 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
heap_size: MAX_HEAP_FRAME_BYTES,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(1),
],
Err(TransactionError::InstructionError(
3,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 7,
..ComputeBudget::default()
}
);
// Combined
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Ok(PrioritizationFeeDetails::new(
PrioritizationFeeType::ComputeUnitPrice(u64::MAX),
MAX_COMPUTE_UNIT_LIMIT as u64,
)),
ComputeBudget {
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
heap_size: MAX_HEAP_FRAME_BYTES,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(1),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Ok(PrioritizationFeeDetails::new(
PrioritizationFeeType::ComputeUnitPrice(u64::MAX),
1
)),
ComputeBudget {
compute_unit_limit: 1,
heap_size: MAX_HEAP_FRAME_BYTES,
..ComputeBudget::default()
}
);
// Duplicates
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_price(0),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
// deprecated
test!(
&[Instruction::new_with_borsh(
compute_budget::id(),
&compute_budget::ComputeBudgetInstruction::RequestUnitsDeprecated {
units: 1_000,
additional_fee: 10
},
vec![]
)],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
}
#[test]
fn test_process_loaded_accounts_data_size_limit_instruction() {
// Assert for empty instructions, change value of support_set_loaded_accounts_data_size_limit_ix
// will not change results, which should all be default
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
test!(
&[],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 0,
..ComputeBudget::default()
},
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents,
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set with data_size
// else
// return InstructionError
let data_size: usize = 1;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let (expected_result, expected_budget) =
if support_set_loaded_accounts_data_size_limit_ix {
(
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
loaded_accounts_data_size_limit: data_size,
..ComputeBudget::default()
},
)
} else {
(
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
)
};
test!(
&[
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
expected_result,
expected_budget,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents, with greater than max value
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set to max data size
// else
// return InstructionError
let data_size: usize = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + 1;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let (expected_result, expected_budget) =
if support_set_loaded_accounts_data_size_limit_ix {
(
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
..ComputeBudget::default()
},
)
} else {
(
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
)
};
test!(
&[
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
expected_result,
expected_budget,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit is not presented
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set to default data size
// else
// return
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let (expected_result, expected_budget) = (
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
..ComputeBudget::default()
},
);
test!(
&[Instruction::new_with_bincode(
Pubkey::new_unique(),
&0_u8,
vec![]
),],
expected_result,
expected_budget,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents more than once,
// if support_set_loaded_accounts_data_size_limit_ix then
// return DuplicateInstruction
// else
// return InstructionError
let data_size: usize = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let (expected_result, expected_budget) =
if support_set_loaded_accounts_data_size_limit_ix {
(
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default(),
)
} else {
(
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
)
};
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32),
],
expected_result,
expected_budget,
support_set_loaded_accounts_data_size_limit_ix
);
}
}
#[test]
fn test_process_mixed_instructions_without_compute_budget() {
let payer_keypair = Keypair::new();
let transaction =
SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2),
],
Some(&payer_keypair.pubkey()),
&[&payer_keypair],
Hash::default(),
));
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_instructions(
transaction.message().program_instructions_iter(),
false, //not support request_units_deprecated
true, //support_set_loaded_accounts_data_size_limit_ix,
);
// assert process_instructions will be successful with default,
assert_eq!(Ok(PrioritizationFeeDetails::default()), result);
// assert the default compute_unit_limit is 2 times default: one for bpf ix, one for
// builtin ix.
assert_eq!(
compute_budget,
ComputeBudget {
compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
..ComputeBudget::default()
}
);
}
}

View File

@ -0,0 +1,619 @@
//! Process compute_budget instructions to extract and sanitize limits.
use {
crate::{
compute_budget::DEFAULT_HEAP_COST,
prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
},
solana_sdk::{
borsh0_10::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
feature_set::{
add_set_tx_loaded_accounts_data_size_instruction, remove_deprecated_request_unit_ix,
FeatureSet,
},
fee::FeeBudgetLimits,
instruction::{CompiledInstruction, InstructionError},
pubkey::Pubkey,
transaction::TransactionError,
},
};
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
/// The total accounts data a transaction can load is limited to 64MiB to not break
/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction
pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: u32 = 64 * 1024 * 1024;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ComputeBudgetLimits {
pub updated_heap_bytes: u32,
pub compute_unit_limit: u32,
pub compute_unit_price: u64,
pub loaded_accounts_bytes: u32,
}
impl Default for ComputeBudgetLimits {
fn default() -> Self {
ComputeBudgetLimits {
updated_heap_bytes: u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap(),
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
compute_unit_price: 0,
loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
}
}
}
impl From<ComputeBudgetLimits> for FeeBudgetLimits {
fn from(val: ComputeBudgetLimits) -> Self {
let prioritization_fee_details = PrioritizationFeeDetails::new(
PrioritizationFeeType::ComputeUnitPrice(val.compute_unit_price),
u64::from(val.compute_unit_limit),
);
FeeBudgetLimits {
// NOTE - usize::from(u32).unwrap() may fail if target is 16-bit and
// `loaded_accounts_bytes` is greater than u16::MAX. In that case, panic is proper.
loaded_accounts_data_size_limit: usize::try_from(val.loaded_accounts_bytes).unwrap(),
heap_cost: DEFAULT_HEAP_COST,
compute_unit_limit: u64::from(val.compute_unit_limit),
prioritization_fee: prioritization_fee_details.get_fee(),
}
}
}
/// Processing compute_budget could be part of tx sanitizing, failed to process
/// these instructions will drop the transaction eventually without execution,
/// may as well fail it early.
/// If succeeded, the transaction's specific limits/requests (could be default)
/// are retrieved and returned,
pub fn process_compute_budget_instructions<'a>(
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
feature_set: &FeatureSet,
) -> Result<ComputeBudgetLimits, TransactionError> {
let support_request_units_deprecated =
!feature_set.is_active(&remove_deprecated_request_unit_ix::id());
let support_set_loaded_accounts_data_size_limit_ix =
feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id());
let mut num_non_compute_budget_instructions: u32 = 0;
let mut updated_compute_unit_limit = None;
let mut updated_compute_unit_price = None;
let mut requested_heap_size = None;
let mut updated_loaded_accounts_data_size_limit = None;
for (i, (program_id, instruction)) in instructions.enumerate() {
if compute_budget::check_id(program_id) {
let invalid_instruction_data_error = TransactionError::InstructionError(
i as u8,
InstructionError::InvalidInstructionData,
);
let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8);
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnitsDeprecated {
units: compute_unit_limit,
additional_fee,
}) if support_request_units_deprecated => {
if updated_compute_unit_limit.is_some() {
return Err(duplicate_instruction_error);
}
if updated_compute_unit_price.is_some() {
return Err(duplicate_instruction_error);
}
updated_compute_unit_limit = Some(compute_unit_limit);
updated_compute_unit_price =
support_deprecated_requested_units(additional_fee, compute_unit_limit);
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if requested_heap_size.is_some() {
return Err(duplicate_instruction_error);
}
if sanitize_requested_heap_size(bytes) {
requested_heap_size = Some(bytes);
} else {
return Err(invalid_instruction_data_error);
}
}
Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => {
if updated_compute_unit_limit.is_some() {
return Err(duplicate_instruction_error);
}
updated_compute_unit_limit = Some(compute_unit_limit);
}
Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => {
if updated_compute_unit_price.is_some() {
return Err(duplicate_instruction_error);
}
updated_compute_unit_price = Some(micro_lamports);
}
Ok(ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(bytes))
if support_set_loaded_accounts_data_size_limit_ix =>
{
if updated_loaded_accounts_data_size_limit.is_some() {
return Err(duplicate_instruction_error);
}
updated_loaded_accounts_data_size_limit = Some(bytes);
}
_ => return Err(invalid_instruction_data_error),
}
} else {
// only include non-request instructions in default max calc
num_non_compute_budget_instructions =
num_non_compute_budget_instructions.saturating_add(1);
}
}
// sanitize limits
let updated_heap_bytes = requested_heap_size
.unwrap_or(u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()) // loader's default heap_size
.min(MAX_HEAP_FRAME_BYTES);
let compute_unit_limit = updated_compute_unit_limit
.unwrap_or_else(|| {
num_non_compute_budget_instructions
.saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT)
})
.min(MAX_COMPUTE_UNIT_LIMIT);
let compute_unit_price = updated_compute_unit_price.unwrap_or(0);
let loaded_accounts_bytes = updated_loaded_accounts_data_size_limit
.unwrap_or(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES)
.min(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES);
Ok(ComputeBudgetLimits {
updated_heap_bytes,
compute_unit_limit,
compute_unit_price,
loaded_accounts_bytes,
})
}
fn sanitize_requested_heap_size(bytes: u32) -> bool {
(u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()..=MAX_HEAP_FRAME_BYTES).contains(&bytes)
&& bytes % 1024 == 0
}
// Supports request_units_derpecated ix, returns cu_price if available.
fn support_deprecated_requested_units(additional_fee: u32, compute_unit_limit: u32) -> Option<u64> {
// TODO: remove support of 'Deprecated' after feature remove_deprecated_request_unit_ix::id() is activated
const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000;
let micro_lamport_fee =
(additional_fee as u128).saturating_mul(MICRO_LAMPORTS_PER_LAMPORT as u128);
micro_lamport_fee
.checked_div(compute_unit_limit as u128)
.map(|cu_price| u64::try_from(cu_price).unwrap_or(u64::MAX))
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_sdk::{
hash::Hash,
instruction::Instruction,
message::Message,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
system_instruction::{self},
transaction::{SanitizedTransaction, Transaction},
},
};
macro_rules! test {
( $instructions: expr, $expected_result: expr, $support_set_loaded_accounts_data_size_limit_ix: expr ) => {
let payer_keypair = Keypair::new();
let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
&[&payer_keypair],
Message::new($instructions, Some(&payer_keypair.pubkey())),
Hash::default(),
));
let mut feature_set = FeatureSet::default();
feature_set.activate(&remove_deprecated_request_unit_ix::id(), 0);
if $support_set_loaded_accounts_data_size_limit_ix {
feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0);
}
let result = process_compute_budget_instructions(
tx.message().program_instructions_iter(),
&feature_set,
);
assert_eq!($expected_result, result);
};
( $instructions: expr, $expected_result: expr ) => {
test!($instructions, $expected_result, false);
};
}
#[test]
fn test_process_instructions() {
// Units
test!(
&[],
Ok(ComputeBudgetLimits {
compute_unit_limit: 0,
..ComputeBudgetLimits::default()
})
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: 1,
..ComputeBudgetLimits::default()
})
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
..ComputeBudgetLimits::default()
})
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
..ComputeBudgetLimits::default()
})
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(1),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: 1,
..ComputeBudgetLimits::default()
})
);
test!(
&[
ComputeBudgetInstruction::set_compute_unit_limit(1),
ComputeBudgetInstruction::set_compute_unit_price(42)
],
Ok(ComputeBudgetLimits {
compute_unit_limit: 1,
compute_unit_price: 42,
..ComputeBudgetLimits::default()
})
);
// HeapFrame
test!(
&[],
Ok(ComputeBudgetLimits {
compute_unit_limit: 0,
..ComputeBudgetLimits::default()
})
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
updated_heap_bytes: 40 * 1024,
..ComputeBudgetLimits::default()
})
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(31 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
);
test!(
&[
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
..ComputeBudgetLimits::default()
})
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(1),
],
Err(TransactionError::InstructionError(
3,
InstructionError::InvalidInstructionData,
))
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 7,
..ComputeBudgetLimits::default()
})
);
// Combined
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Ok(ComputeBudgetLimits {
compute_unit_price: u64::MAX,
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
..ComputeBudgetLimits::default()
})
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(1),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Ok(ComputeBudgetLimits {
compute_unit_price: u64::MAX,
compute_unit_limit: 1,
updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
..ComputeBudgetLimits::default()
})
);
// Duplicates
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1),
],
Err(TransactionError::DuplicateInstruction(2))
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
],
Err(TransactionError::DuplicateInstruction(2))
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_compute_unit_price(0),
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
],
Err(TransactionError::DuplicateInstruction(2))
);
// deprecated
test!(
&[Instruction::new_with_borsh(
compute_budget::id(),
&compute_budget::ComputeBudgetInstruction::RequestUnitsDeprecated {
units: 1_000,
additional_fee: 10
},
vec![]
)],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
);
}
#[test]
fn test_process_loaded_accounts_data_size_limit_instruction() {
// Assert for empty instructions, change value of support_set_loaded_accounts_data_size_limit_ix
// will not change results, which should all be default
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
test!(
&[],
Ok(ComputeBudgetLimits {
compute_unit_limit: 0,
..ComputeBudgetLimits::default()
}),
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents,
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set with data_size
// else
// return InstructionError
let data_size = 1;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let expected_result = if support_set_loaded_accounts_data_size_limit_ix {
Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
loaded_accounts_bytes: data_size,
..ComputeBudgetLimits::default()
})
} else {
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
};
test!(
&[
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
expected_result,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents, with greater than max value
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set to max data size
// else
// return InstructionError
let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + 1;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let expected_result = if support_set_loaded_accounts_data_size_limit_ix {
Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
..ComputeBudgetLimits::default()
})
} else {
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
};
test!(
&[
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
expected_result,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit is not presented
// if support_set_loaded_accounts_data_size_limit_ix then
// budget is set to default data size
// else
// return
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let expected_result = Ok(ComputeBudgetLimits {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
..ComputeBudgetLimits::default()
});
test!(
&[Instruction::new_with_bincode(
Pubkey::new_unique(),
&0_u8,
vec![]
),],
expected_result,
support_set_loaded_accounts_data_size_limit_ix
);
}
// Assert when set_loaded_accounts_data_size_limit presents more than once,
// if support_set_loaded_accounts_data_size_limit_ix then
// return DuplicateInstruction
// else
// return InstructionError
let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES;
for support_set_loaded_accounts_data_size_limit_ix in [true, false] {
let expected_result = if support_set_loaded_accounts_data_size_limit_ix {
Err(TransactionError::DuplicateInstruction(2))
} else {
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidInstructionData,
))
};
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
],
expected_result,
support_set_loaded_accounts_data_size_limit_ix
);
}
}
#[test]
fn test_process_mixed_instructions_without_compute_budget() {
let payer_keypair = Keypair::new();
let transaction =
SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2),
],
Some(&payer_keypair.pubkey()),
&[&payer_keypair],
Hash::default(),
));
let mut feature_set = FeatureSet::default();
feature_set.activate(&remove_deprecated_request_unit_ix::id(), 0);
feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0);
let result = process_compute_budget_instructions(
transaction.message().program_instructions_iter(),
&feature_set,
);
// assert process_instructions will be successful with default,
// and the default compute_unit_limit is 2 times default: one for bpf ix, one for
// builtin ix.
assert_eq!(
result,
Ok(ComputeBudgetLimits {
compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
..ComputeBudgetLimits::default()
})
);
}
}

View File

@ -756,7 +756,7 @@ pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut Invo
mod tests {
use {
super::*,
crate::compute_budget,
crate::compute_budget_processor,
serde::{Deserialize, Serialize},
solana_sdk::{account::WritableAccount, instruction::Instruction, rent::Rent},
};
@ -1084,8 +1084,9 @@ mod tests {
vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
invoke_context.compute_budget =
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
invoke_context.compute_budget = ComputeBudget::new(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
);
invoke_context
.transaction_context
@ -1095,7 +1096,9 @@ mod tests {
invoke_context.push().unwrap();
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64)
ComputeBudget::new(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64
)
);
invoke_context.pop().unwrap();
}

View File

@ -12,6 +12,7 @@ extern crate solana_metrics;
pub use solana_rbpf;
pub mod accounts_data_meter;
pub mod compute_budget;
pub mod compute_budget_processor;
pub mod invoke_context;
pub mod loaded_programs;
pub mod log_collector;

View File

@ -20,7 +20,10 @@ use {
TransactionResults,
},
solana_ledger::token_balances::collect_token_balances,
solana_program_runtime::{compute_budget::ComputeBudget, timings::ExecuteTimings},
solana_program_runtime::{
compute_budget::ComputeBudget,
compute_budget_processor::process_compute_budget_instructions, timings::ExecuteTimings,
},
solana_rbpf::vm::ContextObject,
solana_runtime::{
bank::TransactionBalancesSet,
@ -3835,10 +3838,12 @@ fn test_program_fees() {
let expected_normal_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,
&ComputeBudget::fee_budget_limits(
&process_compute_budget_instructions(
sanitized_message.program_instructions_iter(),
&feature_set,
),
)
.unwrap_or_default()
.into(),
true,
false,
);
@ -3862,10 +3867,12 @@ fn test_program_fees() {
let expected_prioritized_fee = fee_structure.calculate_fee(
&sanitized_message,
congestion_multiplier,
&ComputeBudget::fee_budget_limits(
&process_compute_budget_instructions(
sanitized_message.program_instructions_iter(),
&feature_set,
),
)
.unwrap_or_default()
.into(),
true,
false,
);

View File

@ -105,7 +105,8 @@ use {
solana_perf::perf_libs,
solana_program_runtime::{
accounts_data_meter::MAX_ACCOUNTS_DATA_LEN,
compute_budget::{self, ComputeBudget},
compute_budget::ComputeBudget,
compute_budget_processor::process_compute_budget_instructions,
invoke_context::ProcessInstructionWithContext,
loaded_programs::{
LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
@ -135,10 +136,8 @@ use {
epoch_schedule::EpochSchedule,
feature,
feature_set::{
self, add_set_tx_loaded_accounts_data_size_instruction,
include_loaded_accounts_data_size_in_fee_calculation,
remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix,
FeatureSet,
self, include_loaded_accounts_data_size_in_fee_calculation,
remove_congestion_multiplier_from_fee_calculation, FeatureSet,
},
fee::FeeStructure,
fee_calculator::{FeeCalculator, FeeRateGovernor},
@ -4102,10 +4101,12 @@ impl Bank {
self.fee_structure.calculate_fee(
message,
lamports_per_signature,
&ComputeBudget::fee_budget_limits(
&process_compute_budget_instructions(
message.program_instructions_iter(),
&self.feature_set,
),
)
.unwrap_or_default()
.into(),
self.feature_set
.is_active(&remove_congestion_multiplier_from_fee_calculation::id()),
self.feature_set
@ -5191,36 +5192,28 @@ impl Bank {
.map(|(accs, tx)| match accs {
(Err(e), _nonce) => TransactionExecutionResult::NotExecuted(e.clone()),
(Ok(loaded_transaction), nonce) => {
let compute_budget = if let Some(compute_budget) =
self.runtime_config.compute_budget
{
compute_budget
} else {
let mut compute_budget =
ComputeBudget::new(compute_budget::MAX_COMPUTE_UNIT_LIMIT as u64);
let mut compute_budget_process_transaction_time =
Measure::start("compute_budget_process_transaction_time");
let process_transaction_result = compute_budget.process_instructions(
tx.message().program_instructions_iter(),
!self
.feature_set
.is_active(&remove_deprecated_request_unit_ix::id()),
self.feature_set
.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()),
);
compute_budget_process_transaction_time.stop();
saturating_add_assign!(
timings
.execute_accessories
.compute_budget_process_transaction_us,
compute_budget_process_transaction_time.as_us()
);
if let Err(err) = process_transaction_result {
return TransactionExecutionResult::NotExecuted(err);
}
compute_budget
};
let compute_budget =
if let Some(compute_budget) = self.runtime_config.compute_budget {
compute_budget
} else {
let mut compute_budget_process_transaction_time =
Measure::start("compute_budget_process_transaction_time");
let maybe_compute_budget = ComputeBudget::try_from_instructions(
tx.message().program_instructions_iter(),
&self.feature_set,
);
compute_budget_process_transaction_time.stop();
saturating_add_assign!(
timings
.execute_accessories
.compute_budget_process_transaction_us,
compute_budget_process_transaction_time.as_us()
);
if let Err(err) = maybe_compute_budget {
return TransactionExecutionResult::NotExecuted(err);
}
maybe_compute_budget.unwrap()
};
let result = self.execute_loaded_transaction(
tx,

View File

@ -46,7 +46,8 @@ use {
},
solana_logger,
solana_program_runtime::{
compute_budget::{self, ComputeBudget, MAX_COMPUTE_UNIT_LIMIT},
compute_budget::ComputeBudget,
compute_budget_processor::{self, MAX_COMPUTE_UNIT_LIMIT},
declare_process_instruction,
invoke_context::mock_process_instruction,
loaded_programs::{LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET},
@ -10120,7 +10121,9 @@ fn test_compute_budget_program_noop() {
assert_eq!(
*compute_budget,
ComputeBudget {
compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
compute_unit_limit: u64::from(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
),
heap_size: 48 * 1024,
..ComputeBudget::default()
}
@ -10133,7 +10136,7 @@ fn test_compute_budget_program_noop() {
let message = Message::new(
&[
ComputeBudgetInstruction::set_compute_unit_limit(
compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
),
ComputeBudgetInstruction::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]),
@ -10163,7 +10166,9 @@ fn test_compute_request_instruction() {
assert_eq!(
*compute_budget,
ComputeBudget {
compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
compute_unit_limit: u64::from(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
),
heap_size: 48 * 1024,
..ComputeBudget::default()
}
@ -10176,7 +10181,7 @@ fn test_compute_request_instruction() {
let message = Message::new(
&[
ComputeBudgetInstruction::set_compute_unit_limit(
compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
),
ComputeBudgetInstruction::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]),
@ -10213,7 +10218,9 @@ fn test_failed_compute_request_instruction() {
assert_eq!(
*compute_budget,
ComputeBudget {
compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
compute_unit_limit: u64::from(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
),
heap_size: 48 * 1024,
..ComputeBudget::default()
}
@ -10444,14 +10451,19 @@ fn calculate_test_fee(
remove_congestion_multiplier: bool,
) -> u64 {
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&remove_deprecated_request_unit_ix::id());
feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id());
if !support_set_accounts_data_size_limit_ix {
feature_set.deactivate(&include_loaded_accounts_data_size_in_fee_calculation::id());
feature_set.deactivate(
&solana_sdk::feature_set::include_loaded_accounts_data_size_in_fee_calculation::id(),
);
}
let budget_limits =
ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set);
process_compute_budget_instructions(message.program_instructions_iter(), &feature_set)
.unwrap_or_default()
.into();
fee_structure.calculate_fee(
message,
lamports_per_signature,
@ -11478,7 +11490,9 @@ fn test_rent_state_list_len() {
);
let compute_budget = bank.runtime_config.compute_budget.unwrap_or_else(|| {
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64)
ComputeBudget::new(u64::from(
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
))
});
let transaction_context = TransactionContext::new(
loaded_txs[0].0.as_ref().unwrap().accounts.clone(),

View File

@ -1,6 +1,7 @@
use {
solana_program_runtime::compute_budget::ComputeBudget,
solana_program_runtime::compute_budget_processor::process_compute_budget_instructions,
solana_sdk::{
feature_set::FeatureSet,
instruction::CompiledInstruction,
pubkey::Pubkey,
transaction::{SanitizedTransaction, SanitizedVersionedTransaction},
@ -23,18 +24,17 @@ pub trait GetTransactionPriorityDetails {
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
_round_compute_unit_price_enabled: bool,
) -> Option<TransactionPriorityDetails> {
let mut compute_budget = ComputeBudget::default();
let prioritization_fee_details = compute_budget
.process_instructions(
instructions,
true, // supports prioritization by request_units_deprecated instruction
true, // enable support set accounts data size instruction
// TODO: round_compute_unit_price_enabled: bool
)
.ok()?;
let mut feature_set = FeatureSet::default();
feature_set.activate(
&solana_sdk::feature_set::add_set_tx_loaded_accounts_data_size_instruction::id(),
0,
);
let compute_budget_limits =
process_compute_budget_instructions(instructions, &feature_set).ok()?;
Some(TransactionPriorityDetails {
priority: prioritization_fee_details.get_priority(),
compute_unit_limit: compute_budget.compute_unit_limit,
priority: compute_budget_limits.compute_unit_price,
compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit),
})
}
}
@ -98,8 +98,8 @@ mod tests {
Some(TransactionPriorityDetails {
priority: 0,
compute_unit_limit:
solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64
solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64,
})
);
@ -111,8 +111,8 @@ mod tests {
Some(TransactionPriorityDetails {
priority: 0,
compute_unit_limit:
solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64
solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64,
})
);
}
@ -174,8 +174,8 @@ mod tests {
Some(TransactionPriorityDetails {
priority: requested_price,
compute_unit_limit:
solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64
solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64,
})
);
@ -187,8 +187,8 @@ mod tests {
Some(TransactionPriorityDetails {
priority: requested_price,
compute_unit_limit:
solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64
solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
as u64,
})
);
}