Skip to content

Commit

Permalink
add compute budget instruction to set loaded accounts data size limit (
Browse files Browse the repository at this point in the history
…solana-labs#30377)

* add compute budget instruction to set accounts data size limit

* changes names to explicitly for loaded accounts data size
  • Loading branch information
tao-stones authored and nickfrosty committed Mar 12, 2023
1 parent de50782 commit 92610d1
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 105 deletions.
20 changes: 18 additions & 2 deletions docs/src/developing/programming-model/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ to.
As the transaction is processed compute units are consumed by its
instruction's programs performing operations such as executing SBF instructions,
calling syscalls, etc... When the transaction consumes its entire budget, or
exceeds a bound such as attempting a call stack that is too deep, the runtime
halts the transaction processing and returns an error.
exceeds a bound such as attempting a call stack that is too deep, or loaded
account data size exceeds limit, the runtime halts the transaction processing and
returns an error.

The following operations incur a compute cost:

Expand Down Expand Up @@ -129,6 +130,21 @@ You can learn more of the specifics of _how_ and _when_ to set a prioritization
on the [transaction fees](./../../transaction_fees.md#prioritization-fee) page.
:::

### Accounts data size limit

A transaction should request the maximum bytes of accounts data it is
allowed to load by including a `SetLoadedAccountsDataSizeLimit` instruction, requested
limit is capped by `MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES`. If no
`SetLoadedAccountsDataSizeLimit` is provided, the transaction is defaulted to
have limit of `MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES`.

The `ComputeBudgetInstruction::set_loaded_accounts_data_size_limit` function can be used
to create this instruction:

```rust
let instruction = ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(100_000);
```

## New Features

As Solana evolves, new features or patches may be introduced that changes the
Expand Down
201 changes: 199 additions & 2 deletions program-runtime/src/compute_budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use {
},
};

/// 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;
Expand Down Expand Up @@ -109,6 +113,9 @@ 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,
}

impl Default for ComputeBudget {
Expand Down Expand Up @@ -157,6 +164,7 @@ impl ComputeBudget {
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,
}
}

Expand All @@ -166,11 +174,13 @@ impl ComputeBudget {
default_units_per_instruction: bool,
support_request_units_deprecated: bool,
enable_request_heap_frame_ix: bool,
support_set_loaded_accounts_data_size_limit_ix: bool,
) -> Result<PrioritizationFeeDetails, TransactionError> {
let mut num_non_compute_budget_instructions: usize = 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) {
Expand Down Expand Up @@ -214,6 +224,14 @@ impl ComputeBudget {
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 {
Expand Down Expand Up @@ -250,6 +268,10 @@ impl ComputeBudget {
.unwrap_or(MAX_COMPUTE_UNIT_LIMIT)
.min(MAX_COMPUTE_UNIT_LIMIT) as u64;

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())
Expand All @@ -272,7 +294,7 @@ mod tests {
};

macro_rules! test {
( $instructions: expr, $expected_result: expr, $expected_budget: expr, $enable_request_heap_frame_ix: expr ) => {
( $instructions: expr, $expected_result: expr, $expected_budget: expr, $enable_request_heap_frame_ix: 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],
Expand All @@ -285,12 +307,19 @@ mod tests {
true,
false, /*not support request_units_deprecated*/
$enable_request_heap_frame_ix,
$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, true);
test!(
$instructions,
$expected_result,
$expected_budget,
true,
false
);
};
}

Expand Down Expand Up @@ -561,6 +590,7 @@ mod tests {
compute_unit_limit: 0,
..ComputeBudget::default()
},
false,
false
);

Expand All @@ -575,6 +605,7 @@ mod tests {
InstructionError::InvalidInstructionData
)),
ComputeBudget::default(),
false,
false
);
test!(
Expand All @@ -587,6 +618,7 @@ mod tests {
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
false,
false
);
test!(
Expand All @@ -601,6 +633,7 @@ mod tests {
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
false,
false
);
test!(
Expand All @@ -615,6 +648,7 @@ mod tests {
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
false,
false
);

Expand All @@ -635,7 +669,170 @@ mod tests {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 7,
..ComputeBudget::default()
},
false,
false
);
}

#[test]
fn test_process_loaded_accounts_data_size_limit_instruction() {
let enable_request_heap_frame_ix: bool = true;

// 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()
},
enable_request_heap_frame_ix,
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,
enable_request_heap_frame_ix,
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,
enable_request_heap_frame_ix,
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,
enable_request_heap_frame_ix,
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,
enable_request_heap_frame_ix,
support_set_loaded_accounts_data_size_limit_ix
);
}
}
}
2 changes: 2 additions & 0 deletions programs/sbf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3590,6 +3590,7 @@ fn test_program_fees() {
false,
true,
true,
true,
);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
Expand All @@ -3614,6 +3615,7 @@ fn test_program_fees() {
false,
true,
true,
true,
);
assert!(expected_normal_fee < expected_prioritized_fee);

Expand Down
7 changes: 5 additions & 2 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use {
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot},
feature_set::{
self, enable_request_heap_frame_ix, remove_congestion_multiplier_from_fee_calculation,
remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation, FeatureSet,
self, add_set_tx_loaded_accounts_data_size_instruction, enable_request_heap_frame_ix,
remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix,
use_default_units_in_fee_calculation, FeatureSet,
},
fee::FeeStructure,
genesis_config::ClusterType,
Expand Down Expand Up @@ -640,6 +641,7 @@ impl Accounts {
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&remove_congestion_multiplier_from_fee_calculation::id()),
feature_set.is_active(&enable_request_heap_frame_ix::id()) || self.accounts_db.expected_cluster_type() != ClusterType::MainnetBeta,
feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()),
)
} else {
return (Err(TransactionError::BlockhashNotFound), None);
Expand Down Expand Up @@ -1667,6 +1669,7 @@ mod tests {
false,
true,
true,
true,
);
assert_eq!(fee, lamports_per_signature);

Expand Down
Loading

0 comments on commit 92610d1

Please sign in to comment.