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,
|
State as NonceState,
|
||||||
},
|
},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
saturating_add_assign,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
slot_hashes::SlotHashes,
|
slot_hashes::SlotHashes,
|
||||||
system_program,
|
system_program,
|
||||||
|
@ -56,6 +57,7 @@ use {
|
||||||
std::{
|
std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
collections::{hash_map, BinaryHeap, HashMap, HashSet},
|
collections::{hash_map, BinaryHeap, HashMap, HashSet},
|
||||||
|
num::NonZeroUsize,
|
||||||
ops::RangeBounds,
|
ops::RangeBounds,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{
|
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(
|
fn load_transaction_accounts(
|
||||||
&self,
|
&self,
|
||||||
ancestors: &Ancestors,
|
ancestors: &Ancestors,
|
||||||
|
@ -264,6 +305,11 @@ impl Accounts {
|
||||||
|
|
||||||
let set_exempt_rent_epoch_max =
|
let set_exempt_rent_epoch_max =
|
||||||
feature_set.is_active(&solana_sdk::feature_set::set_exempt_rent_epoch_max::id());
|
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
|
let mut accounts = account_keys
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -312,6 +358,12 @@ impl Accounts {
|
||||||
(default_account, 0)
|
(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 !validated_fee_payer && message.is_non_loader_key(i) {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
|
@ -347,6 +399,12 @@ impl Accounts {
|
||||||
.accounts_db
|
.accounts_db
|
||||||
.load_with_fixed_root(ancestors, &programdata_address)
|
.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 =
|
account_dep_index =
|
||||||
Some(account_keys.len().saturating_add(account_deps.len())
|
Some(account_keys.len().saturating_add(account_deps.len())
|
||||||
as IndexOfAccount);
|
as IndexOfAccount);
|
||||||
|
@ -435,6 +493,12 @@ impl Accounts {
|
||||||
if let Some((program_account, _)) =
|
if let Some((program_account, _)) =
|
||||||
self.accounts_db.load_with_fixed_root(ancestors, owner_id)
|
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));
|
accounts.push((*owner_id, program_account));
|
||||||
} else {
|
} else {
|
||||||
error_counters.account_not_found += 1;
|
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<()>>;
|
pub type BankStatusCache = StatusCache<Result<()>>;
|
||||||
#[frozen_abi(digest = "A7T7XohiSoo8FGoCPTsaXAYYugXTkoYnBjQAdBgYHH85")]
|
#[frozen_abi(digest = "AwRx17Bgesvvu9NZVwAoqofq7MU52gkwvjLvjVQpW64S")]
|
||||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||||
|
|
||||||
// Eager rent collection repeats in cyclic manner.
|
// 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_account_cost_limit: usize,
|
||||||
pub would_exceed_max_vote_cost_limit: usize,
|
pub would_exceed_max_vote_cost_limit: usize,
|
||||||
pub would_exceed_account_data_block_limit: usize,
|
pub would_exceed_account_data_block_limit: usize,
|
||||||
|
pub max_loaded_accounts_data_size_exceeded: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionErrorMetrics {
|
impl TransactionErrorMetrics {
|
||||||
|
@ -76,6 +77,10 @@ impl TransactionErrorMetrics {
|
||||||
self.would_exceed_account_data_block_limit,
|
self.would_exceed_account_data_block_limit,
|
||||||
other.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) {
|
pub fn report(&self, id: u32, slot: Slot) {
|
||||||
|
@ -152,6 +157,11 @@ impl TransactionErrorMetrics {
|
||||||
self.would_exceed_account_data_block_limit as i64,
|
self.would_exceed_account_data_block_limit as i64,
|
||||||
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");
|
solana_sdk::declare_id!("4UDcAfQ6EcA6bdcadkeHpkarkhZGJ7Bpq7wTAiRMjkoi");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod cap_transaction_accounts_data_size {
|
||||||
|
solana_sdk::declare_id!("DdLwVYuvDz26JohmgSbA7mjpJFgX5zP2dkp8qsF2C33V");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
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"),
|
(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"),
|
(enable_big_mod_exp_syscall::id(), "add big_mod_exp syscall #28503"),
|
||||||
(disable_builtin_loader_ownership_chains::id(), "disable builtin loader ownership chains #29956"),
|
(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 ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -149,6 +149,10 @@ pub enum TransactionError {
|
||||||
"Transaction results in an account ({account_index}) with insufficient funds for rent"
|
"Transaction results in an account ({account_index}) with insufficient funds for rent"
|
||||||
)]
|
)]
|
||||||
InsufficientFundsForRent { account_index: u8 },
|
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 {
|
impl From<SanitizeError> for TransactionError {
|
||||||
|
|
|
@ -57,6 +57,7 @@ enum TransactionErrorType {
|
||||||
WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29;
|
WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29;
|
||||||
DUPLICATE_INSTRUCTION = 30;
|
DUPLICATE_INSTRUCTION = 30;
|
||||||
INSUFFICIENT_FUNDS_FOR_RENT = 31;
|
INSUFFICIENT_FUNDS_FOR_RENT = 31;
|
||||||
|
MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED = 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionError {
|
message InstructionError {
|
||||||
|
|
|
@ -801,6 +801,7 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
|
||||||
27 => TransactionError::InvalidRentPayingAccount,
|
27 => TransactionError::InvalidRentPayingAccount,
|
||||||
28 => TransactionError::WouldExceedMaxVoteCostLimit,
|
28 => TransactionError::WouldExceedMaxVoteCostLimit,
|
||||||
29 => TransactionError::WouldExceedAccountDataTotalLimit,
|
29 => TransactionError::WouldExceedAccountDataTotalLimit,
|
||||||
|
32 => TransactionError::MaxLoadedAccountsDataSizeExceeded,
|
||||||
_ => return Err("Invalid TransactionError"),
|
_ => return Err("Invalid TransactionError"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -904,6 +905,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
|
||||||
TransactionError::InsufficientFundsForRent { .. } => {
|
TransactionError::InsufficientFundsForRent { .. } => {
|
||||||
tx_by_addr::TransactionErrorType::InsufficientFundsForRent
|
tx_by_addr::TransactionErrorType::InsufficientFundsForRent
|
||||||
}
|
}
|
||||||
|
TransactionError::MaxLoadedAccountsDataSizeExceeded => {
|
||||||
|
tx_by_addr::TransactionErrorType::MaxLoadedAccountsDataSizeExceeded
|
||||||
|
}
|
||||||
} as i32,
|
} as i32,
|
||||||
instruction_error: match transaction_error {
|
instruction_error: match transaction_error {
|
||||||
TransactionError::InstructionError(index, ref instruction_error) => {
|
TransactionError::InstructionError(index, ref instruction_error) => {
|
||||||
|
|
Loading…
Reference in New Issue