Cap accounts data a transaction can load by its requested limit (#27840)

- Add new compute-budget instruction to set transaction-wide accounts data size limit
- Set default accounts data limit to 10MB, and max to 100MB, per transaction;
- Add getters to make changing default and/or max values easier in the future with feature gates;
- added error counter for transactions exceed data size limit
This commit is contained in:
Tao Zhu 2022-11-14 10:29:35 -06:00 committed by GitHub
parent c828031d9a
commit 81dc2e56ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 844 additions and 166 deletions

View File

@ -54,8 +54,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 exceeds limit, the runtime halts the transaction processing and
returns an error.
The following operations incur a compute cost:
@ -144,6 +145,21 @@ let instruction = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
let instruction = ComputeBudgetInstruction::set_compute_unit_price(1);
```
### Accounts data size limit
A transaction should request the maximum bytes of accounts data it is
allowed to load by including a `SetAccountsDataSizeLimit` instruction, requested
limit is capped by `get_max_loaded_accounts_data_limit()`. If no
`SetAccountsDataSizeLimit` is provided, the transaction is defaulted to
have limit of `get_default_loaded_accounts_data_limit()`.
The `ComputeBudgetInstruction::set_accounts_data_size_limit` function can be used
to create this instruction:
```rust
let instruction = ComputeBudgetInstruction::set_accounts_data_size_limit(100_000);
```
## New Features
As Solana evolves, new features or patches may be introduced that changes the

View File

@ -14,6 +14,33 @@ 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;
/// To change `default` and/or `max` values for `accounts_data_size_limit` in the future,
/// add new enum type here, link to feature gate, and implement the enum in
/// `get_default_loaded_accounts_data_limit()` and/or `get_max_loaded_accounts_data_limit()`.
#[derive(Debug)]
pub enum LoadedAccountsDataLimitType {
V0,
// add future versions here
}
/// Get default value of `ComputeBudget::accounts_data_size_limit` if not set specifically. It
/// sets to 10MB initially, may be changed with feature gate.
const DEFAULT_LOADED_ACCOUNTS_DATA_LIMIT: u32 = 10_000_000;
pub fn get_default_loaded_accounts_data_limit(limit_type: &LoadedAccountsDataLimitType) -> u32 {
match limit_type {
LoadedAccountsDataLimitType::V0 => DEFAULT_LOADED_ACCOUNTS_DATA_LIMIT,
}
}
/// Get max value of `ComputeBudget::accounts_data_size_limit`, it caps value user
/// sets via `ComputeBudgetInstruction::set_compute_unit_limit`. It is set to 100MB
/// initially, can be changed with feature gate.
const MAX_LOADED_ACCOUNTS_DATA_LIMIT: u32 = 100_000_000;
pub fn get_max_loaded_accounts_data_limit(limit_type: &LoadedAccountsDataLimitType) -> u32 {
match limit_type {
LoadedAccountsDataLimitType::V0 => MAX_LOADED_ACCOUNTS_DATA_LIMIT,
}
}
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget {
fn example() -> Self {
@ -28,6 +55,8 @@ pub struct ComputeBudget {
/// allowed to consume. Compute units are consumed by program execution,
/// resources they use, etc...
pub compute_unit_limit: u64,
/// Maximum accounts data size, in bytes, that a transaction is allowed to load
pub accounts_data_size_limit: u64,
/// Number of compute units consumed by a log_u64 call
pub log_64_units: u64,
/// Number of compute units consumed by a create_program_address call
@ -111,6 +140,7 @@ impl ComputeBudget {
pub fn new(compute_unit_limit: u64) -> Self {
ComputeBudget {
compute_unit_limit,
accounts_data_size_limit: DEFAULT_LOADED_ACCOUNTS_DATA_LIMIT as u64,
log_64_units: 100,
create_program_address_units: 1500,
invoke_units: 1000,
@ -150,11 +180,14 @@ impl ComputeBudget {
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
default_units_per_instruction: bool,
support_request_units_deprecated: bool,
cap_transaction_accounts_data_size: bool,
loaded_accounts_data_limit_type: LoadedAccountsDataLimitType,
) -> 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_accounts_data_size_limit = None;
for (i, (program_id, instruction)) in instructions.enumerate() {
if compute_budget::check_id(program_id) {
@ -198,6 +231,14 @@ impl ComputeBudget {
prioritization_fee =
Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports));
}
Ok(ComputeBudgetInstruction::SetAccountsDataSizeLimit(bytes))
if cap_transaction_accounts_data_size =>
{
if updated_accounts_data_size_limit.is_some() {
return Err(duplicate_instruction_error);
}
updated_accounts_data_size_limit = Some(bytes);
}
_ => return Err(invalid_instruction_data_error),
}
} else {
@ -233,6 +274,14 @@ impl ComputeBudget {
.unwrap_or(MAX_COMPUTE_UNIT_LIMIT)
.min(MAX_COMPUTE_UNIT_LIMIT) as u64;
self.accounts_data_size_limit = updated_accounts_data_size_limit
.unwrap_or_else(|| {
get_default_loaded_accounts_data_limit(&loaded_accounts_data_limit_type)
})
.min(get_max_loaded_accounts_data_limit(
&loaded_accounts_data_limit_type,
)) as u64;
Ok(prioritization_fee
.map(|fee_type| PrioritizationFeeDetails::new(fee_type, self.compute_unit_limit))
.unwrap_or_default())
@ -267,6 +316,8 @@ mod tests {
tx.message().program_instructions_iter(),
true,
false, /*not support request_units_deprecated*/
true, /*supports cap transaction accounts data size feature*/
LoadedAccountsDataLimitType::V0,
);
assert_eq!($expected_result, result);
assert_eq!(compute_budget, $expected_budget);
@ -532,4 +583,84 @@ mod tests {
ComputeBudget::default()
);
}
#[test]
fn test_process_accounts_data_size_limit_instruction() {
test!(
&[],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 0,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::set_accounts_data_size_limit(1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
accounts_data_size_limit: 1,
..ComputeBudget::default()
}
);
test!(
&[
ComputeBudgetInstruction::set_accounts_data_size_limit(
get_max_loaded_accounts_data_limit(&LoadedAccountsDataLimitType::V0) + 1
),
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
accounts_data_size_limit: get_max_loaded_accounts_data_limit(
&LoadedAccountsDataLimitType::V0
) as u64,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_accounts_data_size_limit(
get_max_loaded_accounts_data_limit(&LoadedAccountsDataLimitType::V0)
),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
accounts_data_size_limit: get_max_loaded_accounts_data_limit(
&LoadedAccountsDataLimitType::V0
) 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_accounts_data_size_limit(1),
],
Ok(PrioritizationFeeDetails::default()),
ComputeBudget {
compute_unit_limit: 3 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
accounts_data_size_limit: 1,
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
ComputeBudgetInstruction::set_accounts_data_size_limit(1),
ComputeBudgetInstruction::set_accounts_data_size_limit(1),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
}
}

View File

@ -21,7 +21,7 @@ pub async fn setup_test_context() -> ProgramTestContext {
pub async fn assert_ix_error(
context: &mut ProgramTestContext,
ix: Instruction,
ixs: &[Instruction],
additional_payer_keypair: Option<&Keypair>,
expected_err: InstructionError,
assertion_failed_msg: &str,
@ -36,7 +36,7 @@ pub async fn assert_ix_error(
}
let transaction = Transaction::new_signed_with_payer(
&[ix],
ixs,
Some(&fee_payer.pubkey()),
&signers,
recent_blockhash,

View File

@ -5,6 +5,7 @@ use {
solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
bpf_loader_upgradeable::{extend_program, id, UpgradeableLoaderState},
compute_budget::ComputeBudgetInstruction,
instruction::InstructionError,
pubkey::Pubkey,
signature::{Keypair, Signer},
@ -103,7 +104,7 @@ async fn test_extend_program_not_upgradeable() {
let payer_address = context.payer.pubkey();
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 42),
&[extend_program(&program_address, Some(&payer_address), 42)],
None,
InstructionError::Immutable,
"should fail because the program data account isn't upgradeable",
@ -142,7 +143,7 @@ async fn test_extend_program_by_zero_bytes() {
let payer_address = context.payer.pubkey();
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 0),
&[extend_program(&program_address, Some(&payer_address), 0)],
None,
InstructionError::InvalidInstructionData,
"should fail because the program data account must be extended by more than 0 bytes",
@ -181,7 +182,12 @@ async fn test_extend_program_past_max_size() {
let payer_address = context.payer.pubkey();
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 1),
&[
extend_program(&program_address, Some(&payer_address), 1),
// To request large transaction accounts data size to allow max sized test
// instruction being loaded and processed.
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
None,
InstructionError::InvalidRealloc,
"should fail because the program data account cannot be extended past the max data size",
@ -238,11 +244,11 @@ async fn test_extend_program_with_invalid_payer() {
assert_ix_error(
&mut context,
extend_program(
&[extend_program(
&program_address,
Some(&payer_with_insufficient_funds.pubkey()),
1024,
),
)],
Some(&payer_with_insufficient_funds),
InstructionError::from(SystemError::ResultWithNegativeLamports),
"should fail because the payer has insufficient funds to cover program data account rent",
@ -251,11 +257,11 @@ async fn test_extend_program_with_invalid_payer() {
assert_ix_error(
&mut context,
extend_program(
&[extend_program(
&program_address,
Some(&payer_with_invalid_owner.pubkey()),
1,
),
)],
Some(&payer_with_invalid_owner),
InstructionError::ExternalAccountLamportSpend,
"should fail because the payer is not a system account",
@ -280,7 +286,7 @@ async fn test_extend_program_with_invalid_payer() {
assert_ix_error(
&mut context,
ix,
&[ix],
None,
InstructionError::PrivilegeEscalation,
"should fail because the payer did not sign",
@ -320,7 +326,7 @@ async fn test_extend_program_without_payer() {
assert_ix_error(
&mut context,
extend_program(&program_address, None, 1024),
&[extend_program(&program_address, None, 1024)],
None,
InstructionError::NotEnoughAccountKeys,
"should fail because program data has insufficient funds to cover rent",
@ -406,7 +412,7 @@ async fn test_extend_program_with_invalid_system_program() {
assert_ix_error(
&mut context,
ix,
&[ix],
None,
InstructionError::MissingAccount,
"should fail because the system program is missing",
@ -459,7 +465,7 @@ async fn test_extend_program_with_mismatch_program_data() {
assert_ix_error(
&mut context,
ix,
&[ix],
None,
InstructionError::InvalidArgument,
"should fail because the program data account doesn't match the program",
@ -510,7 +516,7 @@ async fn test_extend_program_with_readonly_program_data() {
assert_ix_error(
&mut context,
ix,
&[ix],
None,
InstructionError::InvalidArgument,
"should fail because the program data account is not writable",
@ -548,7 +554,7 @@ async fn test_extend_program_with_invalid_program_data_state() {
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 1024),
&[extend_program(&program_address, Some(&payer_address), 1024)],
None,
InstructionError::InvalidAccountData,
"should fail because the program data account state isn't valid",
@ -589,7 +595,7 @@ async fn test_extend_program_with_invalid_program_data_owner() {
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 1024),
&[extend_program(&program_address, Some(&payer_address), 1024)],
None,
InstructionError::InvalidAccountOwner,
"should fail because the program data account owner isn't valid",
@ -640,7 +646,7 @@ async fn test_extend_program_with_readonly_program() {
assert_ix_error(
&mut context,
ix,
&[ix],
None,
InstructionError::InvalidArgument,
"should fail because the program account is not writable",
@ -680,7 +686,7 @@ async fn test_extend_program_with_invalid_program_owner() {
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 1024),
&[extend_program(&program_address, Some(&payer_address), 1024)],
None,
InstructionError::InvalidAccountOwner,
"should fail because the program account owner isn't valid",
@ -720,7 +726,7 @@ async fn test_extend_program_with_invalid_program_state() {
assert_ix_error(
&mut context,
extend_program(&program_address, Some(&payer_address), 1024),
&[extend_program(&program_address, Some(&payer_address), 1024)],
None,
InstructionError::InvalidAccountData,
"should fail because the program account state isn't valid",

View File

@ -10,7 +10,10 @@ use {
parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType,
},
solana_ledger::token_balances::collect_token_balances,
solana_program_runtime::{compute_budget::ComputeBudget, timings::ExecuteTimings},
solana_program_runtime::{
compute_budget::{self, ComputeBudget},
timings::ExecuteTimings,
},
solana_runtime::{
bank::{
DurableNonceFee, InnerInstruction, TransactionBalancesSet, TransactionExecutionDetails,
@ -3014,13 +3017,17 @@ fn test_program_sbf_realloc() {
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_fill(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
1,
&mut bump,
)],
&[
realloc_extend_and_fill(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
1,
&mut bump,
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
),
)
@ -3038,12 +3045,16 @@ fn test_program_sbf_realloc() {
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump
)],
&[
realloc_extend(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3057,7 +3068,10 @@ fn test_program_sbf_realloc() {
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
&[
realloc(&program_id, &pubkey, 0, &mut bump),
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
),
)
@ -3261,14 +3275,18 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_PLUS_ONE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_PLUS_ONE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3283,14 +3301,18 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_TWICE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_TWICE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3513,14 +3535,18 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3582,15 +3608,19 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_INVOKE_MAX_TWICE],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_INVOKE_MAX_TWICE],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3618,14 +3648,18 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
),
)
@ -3643,14 +3677,18 @@ fn test_program_sbf_realloc_invoke() {
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
&[
Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
),
// Request max transaction accounts data size to allow large instruction
ComputeBudgetInstruction::set_accounts_data_size_limit(u32::MAX),
],
Some(&mint_pubkey),
)
)
@ -3815,6 +3853,8 @@ fn test_program_fees() {
&fee_structure,
true,
false,
false,
compute_budget::LoadedAccountsDataLimitType::V0,
);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
@ -3837,6 +3877,8 @@ fn test_program_fees() {
&fee_structure,
true,
false,
false,
compute_budget::LoadedAccountsDataLimitType::V0,
);
assert!(expected_normal_fee < expected_prioritized_fee);

View File

@ -29,14 +29,15 @@ use {
log::*,
rand::{thread_rng, Rng},
solana_address_lookup_table_program::{error::AddressLookupError, state::AddressLookupTable},
solana_program_runtime::compute_budget::ComputeBudget,
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot},
feature_set::{
self, remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation,
FeatureSet,
self, cap_transaction_accounts_data_size, remove_deprecated_request_unit_ix,
use_default_units_in_fee_calculation, FeatureSet,
},
fee::FeeStructure,
genesis_config::ClusterType,
@ -61,6 +62,7 @@ use {
std::{
cmp::Reverse,
collections::{hash_map, BinaryHeap, HashMap, HashSet},
num::NonZeroUsize,
ops::RangeBounds,
path::PathBuf,
sync::{
@ -272,17 +274,31 @@ impl Accounts {
let mut accounts = Vec::with_capacity(account_keys.len());
let mut account_deps = Vec::with_capacity(account_keys.len());
let mut rent_debits = RentDebits::default();
let requested_loaded_accounts_data_size_limit =
if feature_set.is_active(&feature_set::cap_transaction_accounts_data_size::id()) {
let requested_loaded_accounts_data_size =
Self::get_requested_loaded_accounts_data_size_limit(tx, feature_set)?;
Some(requested_loaded_accounts_data_size)
} else {
None
};
let mut accumulated_accounts_data_size: usize = 0;
for (i, key) in account_keys.iter().enumerate() {
let account = if !message.is_non_loader_key(i) {
let (account, loaded_programdata_account_size) = if !message.is_non_loader_key(i) {
// Fill in an empty account for the program slots.
AccountSharedData::default()
(AccountSharedData::default(), 0)
} else {
#[allow(clippy::collapsible_else_if)]
if solana_sdk::sysvar::instructions::check_id(key) {
Self::construct_instructions_account(
message,
feature_set
.is_active(&feature_set::instructions_sysvar_owned_by_sysvar::id()),
(
Self::construct_instructions_account(
message,
feature_set.is_active(
&feature_set::instructions_sysvar_owned_by_sysvar::id(),
),
),
0,
)
} else {
let (mut account, rent) = if let Some(account_override) =
@ -327,6 +343,7 @@ impl Accounts {
validated_fee_payer = true;
}
let mut loaded_programdata_account_size: usize = 0;
if bpf_loader_upgradeable::check_id(account.owner()) {
if message.is_writable(i) && !message.is_upgradeable_loader_present() {
error_counters.invalid_writable_account += 1;
@ -343,6 +360,8 @@ impl Accounts {
.accounts_db
.load_with_fixed_root(ancestors, &programdata_address)
{
loaded_programdata_account_size =
programdata_account.data().len();
account_deps
.push((programdata_address, programdata_account));
} else {
@ -362,9 +381,19 @@ impl Accounts {
tx_rent += rent;
rent_debits.insert(key, rent, account.lamports());
account
(account, loaded_programdata_account_size)
}
};
Self::accumulate_and_check_loaded_account_data_size(
&mut accumulated_accounts_data_size,
account
.data()
.len()
.saturating_add(loaded_programdata_account_size),
requested_loaded_accounts_data_size_limit,
error_counters,
)?;
accounts.push((*key, account));
}
debug_assert_eq!(accounts.len(), account_keys.len());
@ -385,6 +414,8 @@ impl Accounts {
&mut accounts,
instruction.program_id_index as IndexOfAccount,
error_counters,
&mut accumulated_accounts_data_size,
requested_loaded_accounts_data_size_limit,
)
})
.collect::<Result<Vec<Vec<IndexOfAccount>>>>()?;
@ -402,6 +433,22 @@ impl Accounts {
}
}
fn get_requested_loaded_accounts_data_size_limit(
tx: &SanitizedTransaction,
feature_set: &FeatureSet,
) -> Result<NonZeroUsize> {
let mut compute_budget = ComputeBudget::default();
let _prioritization_fee_details = compute_budget.process_instructions(
tx.message().program_instructions_iter(),
feature_set.is_active(&use_default_units_in_fee_calculation::id()),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&cap_transaction_accounts_data_size::id()),
Bank::get_loaded_accounts_data_limit_type(feature_set),
)?;
NonZeroUsize::new(compute_budget.accounts_data_size_limit as usize)
.ok_or(TransactionError::InvalidLoadedAccountsDataSizeLimit)
}
fn validate_fee_payer(
payer_address: &Pubkey,
payer_account: &mut AccountSharedData,
@ -456,15 +503,22 @@ impl Accounts {
accounts: &mut Vec<TransactionAccount>,
mut program_account_index: IndexOfAccount,
error_counters: &mut TransactionErrorMetrics,
accumulated_accounts_data_size: &mut usize,
requested_loaded_accounts_data_size_limit: Option<NonZeroUsize>,
) -> Result<Vec<IndexOfAccount>> {
let mut account_indices = Vec::new();
let mut program_id = match accounts.get(program_account_index as usize) {
Some(program_account) => program_account.0,
None => {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
};
let (mut program_id, already_loaded_as_non_loader) =
match accounts.get(program_account_index as usize) {
Some(program_account) => (
program_account.0,
// program account is already loaded if it's not empty in `accounts`
program_account.1 != AccountSharedData::default(),
),
None => {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
};
let mut depth = 0;
while !native_loader::check_id(&program_id) {
if depth >= 5 {
@ -472,6 +526,7 @@ impl Accounts {
return Err(TransactionError::CallChainTooDeep);
}
depth += 1;
let mut loaded_account_total_size: usize = 0;
program_account_index = match self
.accounts_db
@ -479,6 +534,13 @@ impl Accounts {
{
Some((program_account, _)) => {
let account_index = accounts.len() as IndexOfAccount;
// do not double count account size for program account on top of call chain
// that has already been loaded during load_transaction as non-loader account.
// Other accounts data size in the call chain are counted.
if !(depth == 1 && already_loaded_as_non_loader) {
loaded_account_total_size =
loaded_account_total_size.saturating_add(program_account.data().len());
}
accounts.push((program_id, program_account));
account_index
}
@ -496,6 +558,7 @@ impl Accounts {
// Add loader to chain
let program_owner = *program.owner();
account_indices.insert(0, program_account_index);
if bpf_loader_upgradeable::check_id(&program_owner) {
// The upgradeable loader requires the derived ProgramData account
if let Ok(UpgradeableLoaderState::Program {
@ -508,6 +571,10 @@ impl Accounts {
{
Some((programdata_account, _)) => {
let account_index = accounts.len() as IndexOfAccount;
if !(depth == 1 && already_loaded_as_non_loader) {
loaded_account_total_size = loaded_account_total_size
.saturating_add(programdata_account.data().len());
}
accounts.push((programdata_address, programdata_account));
account_index
}
@ -522,12 +589,43 @@ impl Accounts {
return Err(TransactionError::InvalidProgramForExecution);
}
}
Self::accumulate_and_check_loaded_account_data_size(
accumulated_accounts_data_size,
loaded_account_total_size,
requested_loaded_accounts_data_size_limit,
error_counters,
)?;
program_id = program_owner;
}
Ok(account_indices)
}
/// Accumulate loaded account data size into `accumulated_accounts_data_size`.
/// Returns TransactionErr::MaxLoadedAccountsDataSizeExceeded if
/// `requested_loaded_accounts_data_size_limit` is specified and
/// `accumulated_accounts_data_size` exceeds it.
fn accumulate_and_check_loaded_account_data_size(
accumulated_loaded_accounts_data_size: &mut usize,
account_data_size: usize,
requested_loaded_accounts_data_size_limit: Option<NonZeroUsize>,
error_counters: &mut TransactionErrorMetrics,
) -> Result<()> {
if let Some(requested_loaded_accounts_data_size) = requested_loaded_accounts_data_size_limit
{
*accumulated_loaded_accounts_data_size =
accumulated_loaded_accounts_data_size.saturating_add(account_data_size);
if *accumulated_loaded_accounts_data_size > requested_loaded_accounts_data_size.get() {
error_counters.max_loaded_accounts_data_size_exceeded += 1;
Err(TransactionError::MaxLoadedAccountsDataSizeExceeded)
} else {
Ok(())
}
} else {
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
pub fn load_accounts(
&self,
@ -558,6 +656,8 @@ impl Accounts {
fee_structure,
feature_set.is_active(&use_default_units_in_fee_calculation::id()),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
feature_set.is_active(&cap_transaction_accounts_data_size::id()),
Bank::get_loaded_accounts_data_limit_type(feature_set),
)
} else {
return (Err(TransactionError::BlockhashNotFound), None);
@ -1401,7 +1501,7 @@ mod tests {
},
assert_matches::assert_matches,
solana_address_lookup_table_program::state::LookupTableMeta,
solana_program_runtime::executor_cache::TransactionExecutorCache,
solana_program_runtime::{compute_budget, executor_cache::TransactionExecutorCache},
solana_sdk::{
account::{AccountSharedData, WritableAccount},
epoch_schedule::EpochSchedule,
@ -1659,6 +1759,8 @@ mod tests {
&FeeStructure::default(),
true,
false,
true,
compute_budget::LoadedAccountsDataLimitType::V0,
);
assert_eq!(fee, lamports_per_signature);
@ -2472,6 +2574,8 @@ mod tests {
&mut vec![(keypair.pubkey(), account)],
0,
&mut error_counters,
&mut 0,
None,
),
Err(TransactionError::ProgramAccountNotFound)
);
@ -3900,4 +4004,230 @@ mod tests {
));
}
}
#[test]
fn test_accumulate_and_check_loaded_account_data_size() {
let mut error_counter = TransactionErrorMetrics::default();
// assert check is OK if data limit is not enabled
{
let mut accumulated_data_size: usize = 0;
let data_size = usize::MAX;
let requested_data_size_limit = None;
assert!(Accounts::accumulate_and_check_loaded_account_data_size(
&mut accumulated_data_size,
data_size,
requested_data_size_limit,
&mut error_counter
)
.is_ok());
}
// assert accounts are accumulated and check properly
{
let mut accumulated_data_size: usize = 0;
let data_size: usize = 123;
let requested_data_size_limit = NonZeroUsize::new(data_size);
// OK
assert!(Accounts::accumulate_and_check_loaded_account_data_size(
&mut accumulated_data_size,
data_size,
requested_data_size_limit,
&mut error_counter
)
.is_ok());
assert_eq!(data_size, accumulated_data_size);
// exceeds
let another_byte: usize = 1;
assert_eq!(
Accounts::accumulate_and_check_loaded_account_data_size(
&mut accumulated_data_size,
another_byte,
requested_data_size_limit,
&mut error_counter
),
Err(TransactionError::MaxLoadedAccountsDataSizeExceeded)
);
}
}
#[test]
fn test_load_executable_accounts() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut error_counters = TransactionErrorMetrics::default();
let ancestors = vec![(0, 0)].into_iter().collect();
let space: usize = 9;
let keypair = Keypair::new();
let mut account = AccountSharedData::new(1, space, &native_loader::id());
account.set_executable(true);
accounts.store_slow_uncached(0, &keypair.pubkey(), &account);
let mut accumulated_accounts_data_size: usize = 0;
let mut expect_accumulated_accounts_data_size: usize;
// test: program account has been loaded as non-loader, load_executable_accounts
// will not double count its data size
{
let mut loaded_accounts = vec![(keypair.pubkey(), account)];
accumulated_accounts_data_size += space;
expect_accumulated_accounts_data_size = accumulated_accounts_data_size;
assert!(accounts
.load_executable_accounts(
&ancestors,
&mut loaded_accounts,
0,
&mut error_counters,
&mut accumulated_accounts_data_size,
NonZeroUsize::new(expect_accumulated_accounts_data_size),
)
.is_ok());
assert_eq!(
expect_accumulated_accounts_data_size,
accumulated_accounts_data_size
);
}
// test: program account has not been loaded cause it's loader, load_executable_accounts
// will accumulate its data size
{
let mut loaded_accounts = vec![(keypair.pubkey(), AccountSharedData::default())];
expect_accumulated_accounts_data_size = accumulated_accounts_data_size + space;
assert!(accounts
.load_executable_accounts(
&ancestors,
&mut loaded_accounts,
0,
&mut error_counters,
&mut accumulated_accounts_data_size,
NonZeroUsize::new(expect_accumulated_accounts_data_size),
)
.is_ok());
assert_eq!(
expect_accumulated_accounts_data_size,
accumulated_accounts_data_size
);
}
// test: try to load one more account will accumulate additional `space` bytes, therefore
// exceed limit of `expect_accumulated_accounts_data_size` set above.
{
let mut loaded_accounts = vec![(keypair.pubkey(), AccountSharedData::default())];
assert_eq!(
accounts.load_executable_accounts(
&ancestors,
&mut loaded_accounts,
0,
&mut error_counters,
&mut accumulated_accounts_data_size,
NonZeroUsize::new(expect_accumulated_accounts_data_size),
),
Err(TransactionError::MaxLoadedAccountsDataSizeExceeded)
);
}
}
#[test]
fn test_load_executable_accounts_with_upgradeable_program() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut error_counters = TransactionErrorMetrics::default();
let ancestors = vec![(0, 0)].into_iter().collect();
// call chain: native_loader -> key0 -> upgradeable bpf loader -> program_key (that has programdata_key)
let programdata_key = Pubkey::new(&[3u8; 32]);
let program_data = UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: None,
};
let mut program_data_account =
AccountSharedData::new_data(1, &program_data, &Pubkey::default()).unwrap();
let program_data_account_size = program_data_account.data().len();
program_data_account.set_executable(true);
program_data_account.set_rent_epoch(0);
accounts.store_slow_uncached(0, &programdata_key, &program_data_account);
let program_key = Pubkey::new(&[2u8; 32]);
let program = UpgradeableLoaderState::Program {
programdata_address: programdata_key,
};
let mut program_account =
AccountSharedData::new_data(1, &program, &bpf_loader_upgradeable::id()).unwrap();
let program_account_size = program_account.data().len();
program_account.set_executable(true);
program_account.set_rent_epoch(0);
accounts.store_slow_uncached(0, &program_key, &program_account);
let key0 = Pubkey::new(&[1u8; 32]);
let mut bpf_loader_account = AccountSharedData::new(1, 0, &key0);
bpf_loader_account.set_executable(true);
accounts.store_slow_uncached(0, &bpf_loader_upgradeable::id(), &bpf_loader_account);
let space: usize = 9;
let mut account = AccountSharedData::new(1, space, &native_loader::id());
account.set_executable(true);
accounts.store_slow_uncached(0, &key0, &account);
// test: program_key account has been loaded as non-loader, load_executable_accounts
// will not double count its data size, but still accumulate data size from
// callchain
{
let expect_accumulated_accounts_data_size: usize = space;
let mut accumulated_accounts_data_size: usize = 0;
let mut loaded_accounts = vec![(program_key, program_account)];
assert!(accounts
.load_executable_accounts(
&ancestors,
&mut loaded_accounts,
0,
&mut error_counters,
&mut accumulated_accounts_data_size,
NonZeroUsize::new(expect_accumulated_accounts_data_size),
)
.is_ok());
assert_eq!(
expect_accumulated_accounts_data_size,
accumulated_accounts_data_size
);
}
// test: program account has not been loaded cause it's loader, load_executable_accounts
// will accumulate entire callchain data size.
{
let mut loaded_accounts = vec![(program_key, AccountSharedData::default())];
let mut accumulated_accounts_data_size: usize = 0;
let expect_accumulated_accounts_data_size =
program_account_size + program_data_account_size + space;
assert!(accounts
.load_executable_accounts(
&ancestors,
&mut loaded_accounts,
0,
&mut error_counters,
&mut accumulated_accounts_data_size,
NonZeroUsize::new(expect_accumulated_accounts_data_size),
)
.is_ok());
assert_eq!(
expect_accumulated_accounts_data_size,
accumulated_accounts_data_size
);
}
}
}

View File

@ -116,8 +116,9 @@ use {
epoch_schedule::EpochSchedule,
feature,
feature_set::{
self, disable_fee_calculator, enable_early_verification_of_account_modifications,
remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation, FeatureSet,
self, cap_transaction_accounts_data_size, disable_fee_calculator,
enable_early_verification_of_account_modifications, remove_deprecated_request_unit_ix,
use_default_units_in_fee_calculation, FeatureSet,
},
fee::FeeStructure,
fee_calculator::{FeeCalculator, FeeRateGovernor},
@ -289,7 +290,7 @@ impl RentDebits {
}
pub type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "A7T7XohiSoo8FGoCPTsaXAYYugXTkoYnBjQAdBgYHH85")]
#[frozen_abi(digest = "3qia1Zm8X66bzFaBuC8ahz3hADRRATyUPRV36ZzrSois")]
pub type BankSlotDelta = SlotDelta<Result<()>>;
// Eager rent collection repeats in cyclic manner.
@ -3490,6 +3491,9 @@ impl Bank {
!self
.feature_set
.is_active(&remove_deprecated_request_unit_ix::id()),
self.feature_set
.is_active(&cap_transaction_accounts_data_size::id()),
Self::get_loaded_accounts_data_limit_type(&self.feature_set),
))
}
@ -3534,6 +3538,9 @@ impl Bank {
!self
.feature_set
.is_active(&remove_deprecated_request_unit_ix::id()),
self.feature_set
.is_active(&cap_transaction_accounts_data_size::id()),
Self::get_loaded_accounts_data_limit_type(&self.feature_set),
)
}
@ -4438,6 +4445,9 @@ impl Bank {
!self
.feature_set
.is_active(&remove_deprecated_request_unit_ix::id()),
self.feature_set
.is_active(&cap_transaction_accounts_data_size::id()),
Self::get_loaded_accounts_data_limit_type(&self.feature_set),
);
compute_budget_process_transaction_time.stop();
saturating_add_assign!(
@ -4724,6 +4734,8 @@ impl Bank {
fee_structure: &FeeStructure,
use_default_units_per_instruction: bool,
support_request_units_deprecated: bool,
cap_transaction_accounts_data_size: bool,
loaded_accounts_data_limit_type: compute_budget::LoadedAccountsDataLimitType,
) -> u64 {
// Fee based on compute units and signatures
const BASE_CONGESTION: f64 = 5_000.0;
@ -4740,6 +4752,8 @@ impl Bank {
message.program_instructions_iter(),
use_default_units_per_instruction,
support_request_units_deprecated,
cap_transaction_accounts_data_size,
loaded_accounts_data_limit_type,
)
.unwrap_or_default();
let prioritization_fee = prioritization_fee_details.get_fee();
@ -4808,6 +4822,9 @@ impl Bank {
!self
.feature_set
.is_active(&remove_deprecated_request_unit_ix::id()),
self.feature_set
.is_active(&cap_transaction_accounts_data_size::id()),
Self::get_loaded_accounts_data_limit_type(&self.feature_set),
);
// In case of instruction error, even though no accounts
@ -7742,6 +7759,17 @@ impl Bank {
&mut error_counters,
)
}
/// if the `default` and/or `max` value for ComputeBudget:::Accounts_data_size_limit changes,
/// the change needs to be gated by feature gate with corresponding new enum value.
/// should use this function to get correct loaded_accounts_data_limit_type based on
/// feature_set.
pub fn get_loaded_accounts_data_limit_type(
_feature_set: &FeatureSet,
) -> compute_budget::LoadedAccountsDataLimitType {
compute_budget::LoadedAccountsDataLimitType::V0
// In the future, use feature_set to determine correct LoadedAccountsDataLimitType here.
}
}
/// Compute how much an account has changed size. This function is useful when the data size delta
@ -10803,6 +10831,8 @@ pub(crate) mod tests {
&FeeStructure::default(),
true,
false,
true,
compute_budget::LoadedAccountsDataLimitType::V0,
);
let (expected_fee_collected, expected_fee_burned) =
@ -10984,6 +11014,8 @@ pub(crate) mod tests {
&FeeStructure::default(),
true,
false,
true,
compute_budget::LoadedAccountsDataLimitType::V0,
);
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
@ -11002,6 +11034,8 @@ pub(crate) mod tests {
&FeeStructure::default(),
true,
false,
true,
compute_budget::LoadedAccountsDataLimitType::V0,
);
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
@ -11118,6 +11152,8 @@ pub(crate) mod tests {
&FeeStructure::default(),
true,
false,
true,
compute_budget::LoadedAccountsDataLimitType::V0,
) * 2
)
.0
@ -18161,34 +18197,42 @@ pub(crate) mod tests {
// Default: no fee.
let message =
SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
assert_eq!(
Bank::calculate_fee(
&message,
0,
&FeeStructure {
lamports_per_signature: 0,
..FeeStructure::default()
},
true,
false,
),
0
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
0,
&FeeStructure {
lamports_per_signature: 0,
..FeeStructure::default()
},
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0,
),
0
);
}
// One signature, a fee.
assert_eq!(
Bank::calculate_fee(
&message,
1,
&FeeStructure {
lamports_per_signature: 1,
..FeeStructure::default()
},
true,
false,
),
1
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
1,
&FeeStructure {
lamports_per_signature: 1,
..FeeStructure::default()
},
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0,
),
1
);
}
// Two signatures, double the fee.
let key0 = Pubkey::new_unique();
@ -18196,19 +18240,23 @@ pub(crate) mod tests {
let ix0 = system_instruction::transfer(&key0, &key1, 1);
let ix1 = system_instruction::transfer(&key1, &key0, 1);
let message = SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&key0))).unwrap();
assert_eq!(
Bank::calculate_fee(
&message,
2,
&FeeStructure {
lamports_per_signature: 2,
..FeeStructure::default()
},
true,
false,
),
4
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
2,
&FeeStructure {
lamports_per_signature: 2,
..FeeStructure::default()
},
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0,
),
4
);
}
}
#[test]
@ -18224,10 +18272,20 @@ pub(crate) mod tests {
let message =
SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true, false),
max_fee + lamports_per_signature
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
1,
&fee_structure,
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0
),
max_fee + lamports_per_signature
);
}
// Three signatures, two instructions, no unit request
@ -18236,10 +18294,20 @@ pub(crate) mod tests {
let message =
SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&Pubkey::new_unique())))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true, false),
max_fee + 3 * lamports_per_signature
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
1,
&fee_structure,
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0
),
max_fee + 3 * lamports_per_signature
);
}
// Explicit fee schedule
@ -18270,11 +18338,21 @@ pub(crate) mod tests {
Some(&Pubkey::new_unique()),
))
.unwrap();
let fee = Bank::calculate_fee(&message, 1, &fee_structure, true, false);
assert_eq!(
fee,
lamports_per_signature + prioritization_fee_details.get_fee()
);
for cap_transaction_accounts_data_size in &[true, false] {
let fee = Bank::calculate_fee(
&message,
1,
&fee_structure,
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0,
);
assert_eq!(
fee,
lamports_per_signature + prioritization_fee_details.get_fee()
);
}
}
}
@ -18308,10 +18386,20 @@ pub(crate) mod tests {
Some(&key0),
))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true, false),
2
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
1,
&fee_structure,
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0
),
2
);
}
secp_instruction1.data = vec![0];
secp_instruction2.data = vec![10];
@ -18320,10 +18408,20 @@ pub(crate) mod tests {
Some(&key0),
))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true, false),
11
);
for cap_transaction_accounts_data_size in &[true, false] {
assert_eq!(
Bank::calculate_fee(
&message,
1,
&fee_structure,
true,
false,
*cap_transaction_accounts_data_size,
compute_budget::LoadedAccountsDataLimitType::V0
),
11
);
}
}
#[test]

View File

@ -23,6 +23,8 @@ pub struct TransactionErrorMetrics {
pub would_exceed_max_account_cost_limit: usize,
pub would_exceed_max_vote_cost_limit: usize,
pub would_exceed_account_data_block_limit: usize,
pub max_loaded_accounts_data_size_exceeded: usize,
pub invalid_loaded_accounts_data_size_limit: usize,
}
impl TransactionErrorMetrics {
@ -76,6 +78,14 @@ impl TransactionErrorMetrics {
self.would_exceed_account_data_block_limit,
other.would_exceed_account_data_block_limit
);
saturating_add_assign!(
self.max_loaded_accounts_data_size_exceeded,
other.max_loaded_accounts_data_size_exceeded
);
saturating_add_assign!(
self.invalid_loaded_accounts_data_size_limit,
other.invalid_loaded_accounts_data_size_limit
);
}
pub fn report(&self, id: u32, slot: Slot) {
@ -152,6 +162,16 @@ impl TransactionErrorMetrics {
self.would_exceed_account_data_block_limit as i64,
i64
),
(
"max_loaded_accounts_data_size_exceeded",
self.max_loaded_accounts_data_size_exceeded as i64,
i64
),
(
"invalid_loaded_accounts_data_size_limit",
self.invalid_loaded_accounts_data_size_limit as i64,
i64
),
);
}
}

View File

@ -1,5 +1,5 @@
use {
solana_program_runtime::compute_budget::ComputeBudget,
solana_program_runtime::compute_budget::{self, ComputeBudget},
solana_sdk::{
instruction::CompiledInstruction,
pubkey::Pubkey,
@ -25,6 +25,9 @@ pub trait GetTransactionPriorityDetails {
instructions,
true, // use default units per instruction
false, // stop supporting prioritization by request_units_deprecated instruction
false, //transaction priority doesn't care about accounts data size limit,
compute_budget::LoadedAccountsDataLimitType::V0, // transaction priority doesn't
// care about accounts data size limit.
)
.ok()?;
Some(TransactionPriorityDetails {

View File

@ -39,6 +39,8 @@ pub enum ComputeBudgetInstruction {
/// Set a compute unit price in "micro-lamports" to pay a higher transaction
/// fee for higher transaction prioritization.
SetComputeUnitPrice(u64),
/// Set a specific transaction-wide account data size limit, in bytes, is allowed to allocate.
SetAccountsDataSizeLimit(u32),
}
impl ComputeBudgetInstruction {
@ -56,4 +58,9 @@ impl ComputeBudgetInstruction {
pub fn set_compute_unit_price(micro_lamports: u64) -> Instruction {
Instruction::new_with_borsh(id(), &Self::SetComputeUnitPrice(micro_lamports), vec![])
}
/// Create a `ComputeBudgetInstruction::SetAccountsDataSizeLimit` `Instruction`
pub fn set_accounts_data_size_limit(bytes: u32) -> Instruction {
Instruction::new_with_borsh(id(), &Self::SetAccountsDataSizeLimit(bytes), vec![])
}
}

View File

@ -538,6 +538,10 @@ pub mod enable_bpf_loader_set_authority_checked_ix {
solana_sdk::declare_id!("5x3825XS7M2A3Ekbn5VGGkvFoAg5qrRWkTrY4bARP1GL");
}
pub mod cap_transaction_accounts_data_size {
solana_sdk::declare_id!("DdLwVYuvDz26JohmgSbA7mjpJFgX5zP2dkp8qsF2C33V");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -667,6 +671,7 @@ lazy_static! {
(limit_max_instruction_trace_length::id(), "limit max instruction trace length #27939"),
(check_syscall_outputs_do_not_overlap::id(), "check syscall outputs do_not overlap #28600"),
(enable_bpf_loader_set_authority_checked_ix::id(), "enable bpf upgradeable loader SetAuthorityChecked instruction #28424"),
(cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to its compute unit limits #27839"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -149,6 +149,16 @@ pub enum TransactionError {
"Transaction results in an account ({account_index}) with insufficient funds for rent"
)]
InsufficientFundsForRent { account_index: u8 },
/// Transaction exceeded max loaded accounts data size capped by requested compute units
#[error(
"Transaction exceeded max loaded accounts data size capped by requested compute units"
)]
MaxLoadedAccountsDataSizeExceeded,
/// LoadedAccountsDataSizeLimit set for transaction must be greater than 0.
#[error("LoadedAccountsDataSizeLimit set for transaction must be greater than 0.")]
InvalidLoadedAccountsDataSizeLimit,
}
impl From<SanitizeError> for TransactionError {

View File

@ -57,6 +57,8 @@ enum TransactionErrorType {
WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29;
DUPLICATE_INSTRUCTION = 30;
INSUFFICIENT_FUNDS_FOR_RENT = 31;
MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED = 32;
INVALID_LOADED_ACCOUNTS_DATA_SIZE_LIMIT = 33;
}
message InstructionError {

View File

@ -793,6 +793,8 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
27 => TransactionError::InvalidRentPayingAccount,
28 => TransactionError::WouldExceedMaxVoteCostLimit,
29 => TransactionError::WouldExceedAccountDataTotalLimit,
32 => TransactionError::MaxLoadedAccountsDataSizeExceeded,
33 => TransactionError::InvalidLoadedAccountsDataSizeLimit,
_ => return Err("Invalid TransactionError"),
})
}
@ -896,6 +898,12 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
TransactionError::InsufficientFundsForRent { .. } => {
tx_by_addr::TransactionErrorType::InsufficientFundsForRent
}
TransactionError::MaxLoadedAccountsDataSizeExceeded => {
tx_by_addr::TransactionErrorType::MaxLoadedAccountsDataSizeExceeded
}
TransactionError::InvalidLoadedAccountsDataSizeLimit => {
tx_by_addr::TransactionErrorType::InvalidLoadedAccountsDataSizeLimit
}
} as i32,
instruction_error: match transaction_error {
TransactionError::InstructionError(index, ref instruction_error) => {