Limit loaded data per transaction to a fixed cap (#29743)
This commit is contained in:
parent
9780cd10c4
commit
a5af54669a
|
@ -46,6 +46,7 @@ use {
|
|||
State as NonceState,
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
saturating_add_assign,
|
||||
signature::Signature,
|
||||
slot_hashes::SlotHashes,
|
||||
system_program,
|
||||
|
@ -56,6 +57,7 @@ use {
|
|||
std::{
|
||||
cmp::Reverse,
|
||||
collections::{hash_map, BinaryHeap, HashMap, HashSet},
|
||||
num::NonZeroUsize,
|
||||
ops::RangeBounds,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
|
@ -237,6 +239,45 @@ impl Accounts {
|
|||
})
|
||||
}
|
||||
|
||||
/// If feature `cap_transaction_accounts_data_size` is active, total accounts data a
|
||||
/// transaction can load is limited to 64MiB to not break anyone in Mainnet-beta today.
|
||||
/// (It will be set by compute_budget instruction in the future to more reasonable level).
|
||||
fn get_requested_loaded_accounts_data_size_limit(
|
||||
feature_set: &FeatureSet,
|
||||
) -> Option<NonZeroUsize> {
|
||||
feature_set
|
||||
.is_active(&feature_set::cap_transaction_accounts_data_size::id())
|
||||
.then(|| {
|
||||
const REQUESTED_LOADED_ACCOUNTS_DATA_SIZE: usize = 64 * 1024 * 1024;
|
||||
NonZeroUsize::new(REQUESTED_LOADED_ACCOUNTS_DATA_SIZE)
|
||||
.expect("requested loaded accounts data size is greater than 0")
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
{
|
||||
saturating_add_assign!(*accumulated_loaded_accounts_data_size, 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(())
|
||||
}
|
||||
}
|
||||
|
||||
fn load_transaction_accounts(
|
||||
&self,
|
||||
ancestors: &Ancestors,
|
||||
|
@ -264,6 +305,11 @@ impl Accounts {
|
|||
|
||||
let set_exempt_rent_epoch_max =
|
||||
feature_set.is_active(&solana_sdk::feature_set::set_exempt_rent_epoch_max::id());
|
||||
|
||||
let requested_loaded_accounts_data_size_limit =
|
||||
Self::get_requested_loaded_accounts_data_size_limit(feature_set);
|
||||
let mut accumulated_accounts_data_size: usize = 0;
|
||||
|
||||
let mut accounts = account_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -312,6 +358,12 @@ impl Accounts {
|
|||
(default_account, 0)
|
||||
})
|
||||
};
|
||||
Self::accumulate_and_check_loaded_account_data_size(
|
||||
&mut accumulated_accounts_data_size,
|
||||
account.data().len(),
|
||||
requested_loaded_accounts_data_size_limit,
|
||||
error_counters,
|
||||
)?;
|
||||
|
||||
if !validated_fee_payer && message.is_non_loader_key(i) {
|
||||
if i != 0 {
|
||||
|
@ -347,6 +399,12 @@ impl Accounts {
|
|||
.accounts_db
|
||||
.load_with_fixed_root(ancestors, &programdata_address)
|
||||
{
|
||||
Self::accumulate_and_check_loaded_account_data_size(
|
||||
&mut accumulated_accounts_data_size,
|
||||
programdata_account.data().len(),
|
||||
requested_loaded_accounts_data_size_limit,
|
||||
error_counters,
|
||||
)?;
|
||||
account_dep_index =
|
||||
Some(account_keys.len().saturating_add(account_deps.len())
|
||||
as IndexOfAccount);
|
||||
|
@ -435,6 +493,12 @@ impl Accounts {
|
|||
if let Some((program_account, _)) =
|
||||
self.accounts_db.load_with_fixed_root(ancestors, owner_id)
|
||||
{
|
||||
Self::accumulate_and_check_loaded_account_data_size(
|
||||
&mut accumulated_accounts_data_size,
|
||||
program_account.data().len(),
|
||||
requested_loaded_accounts_data_size_limit,
|
||||
error_counters,
|
||||
)?;
|
||||
accounts.push((*owner_id, program_account));
|
||||
} else {
|
||||
error_counters.account_not_found += 1;
|
||||
|
@ -3735,4 +3799,53 @@ 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 check will fail with correct error if loaded data exceeds limit
|
||||
{
|
||||
let mut accumulated_data_size: usize = 0;
|
||||
let data_size: usize = 123;
|
||||
let requested_data_size_limit = NonZeroUsize::new(data_size);
|
||||
|
||||
// OK - loaded data size is up to limit
|
||||
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);
|
||||
|
||||
// fail - loading more data that would exceed limit
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -278,7 +278,7 @@ impl RentDebits {
|
|||
}
|
||||
|
||||
pub type BankStatusCache = StatusCache<Result<()>>;
|
||||
#[frozen_abi(digest = "A7T7XohiSoo8FGoCPTsaXAYYugXTkoYnBjQAdBgYHH85")]
|
||||
#[frozen_abi(digest = "AwRx17Bgesvvu9NZVwAoqofq7MU52gkwvjLvjVQpW64S")]
|
||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||
|
||||
// Eager rent collection repeats in cyclic manner.
|
||||
|
|
|
@ -23,6 +23,7 @@ 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,
|
||||
}
|
||||
|
||||
impl TransactionErrorMetrics {
|
||||
|
@ -76,6 +77,10 @@ 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
|
||||
);
|
||||
}
|
||||
|
||||
pub fn report(&self, id: u32, slot: Slot) {
|
||||
|
@ -152,6 +157,11 @@ 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
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -594,6 +594,10 @@ pub mod disable_builtin_loader_ownership_chains {
|
|||
solana_sdk::declare_id!("4UDcAfQ6EcA6bdcadkeHpkarkhZGJ7Bpq7wTAiRMjkoi");
|
||||
}
|
||||
|
||||
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> = [
|
||||
|
@ -737,6 +741,7 @@ lazy_static! {
|
|||
(update_hashes_per_tick::id(), "Update desired hashes per tick on epoch boundary"),
|
||||
(enable_big_mod_exp_syscall::id(), "add big_mod_exp syscall #28503"),
|
||||
(disable_builtin_loader_ownership_chains::id(), "disable builtin loader ownership chains #29956"),
|
||||
(cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to a limit #27839"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -149,6 +149,10 @@ 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 cap
|
||||
#[error("Transaction exceeded max loaded accounts data size cap")]
|
||||
MaxLoadedAccountsDataSizeExceeded,
|
||||
}
|
||||
|
||||
impl From<SanitizeError> for TransactionError {
|
||||
|
|
|
@ -57,6 +57,7 @@ enum TransactionErrorType {
|
|||
WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29;
|
||||
DUPLICATE_INSTRUCTION = 30;
|
||||
INSUFFICIENT_FUNDS_FOR_RENT = 31;
|
||||
MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED = 32;
|
||||
}
|
||||
|
||||
message InstructionError {
|
||||
|
|
|
@ -801,6 +801,7 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
|
|||
27 => TransactionError::InvalidRentPayingAccount,
|
||||
28 => TransactionError::WouldExceedMaxVoteCostLimit,
|
||||
29 => TransactionError::WouldExceedAccountDataTotalLimit,
|
||||
32 => TransactionError::MaxLoadedAccountsDataSizeExceeded,
|
||||
_ => return Err("Invalid TransactionError"),
|
||||
})
|
||||
}
|
||||
|
@ -904,6 +905,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
|
|||
TransactionError::InsufficientFundsForRent { .. } => {
|
||||
tx_by_addr::TransactionErrorType::InsufficientFundsForRent
|
||||
}
|
||||
TransactionError::MaxLoadedAccountsDataSizeExceeded => {
|
||||
tx_by_addr::TransactionErrorType::MaxLoadedAccountsDataSizeExceeded
|
||||
}
|
||||
} as i32,
|
||||
instruction_error: match transaction_error {
|
||||
TransactionError::InstructionError(index, ref instruction_error) => {
|
||||
|
|
Loading…
Reference in New Issue