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:
parent
c828031d9a
commit
81dc2e56ac
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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![])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in New Issue