Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 157 additions & 33 deletions runtime/src/account_rent_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ pub(crate) enum RentState {
/// account.lamports == 0
Uninitialized,
/// 0 < account.lamports < rent-exempt-minimum
/// Parameter is the size of the account data
RentPaying(usize),
RentPaying {
lamports: u64, // account.lamports()
data_size: usize, // account.data().len()
},
/// account.lamports >= rent-exempt-minimum
RentExempt,
}
Expand All @@ -24,43 +26,59 @@ impl RentState {
pub(crate) fn from_account(account: &AccountSharedData, rent: &Rent) -> Self {
if account.lamports() == 0 {
Self::Uninitialized
} else if !rent.is_exempt(account.lamports(), account.data().len()) {
Self::RentPaying(account.data().len())
} else {
} else if rent.is_exempt(account.lamports(), account.data().len()) {
Self::RentExempt
} else {
Self::RentPaying {
data_size: account.data().len(),
lamports: account.lamports(),
}
}
}

pub(crate) fn transition_allowed_from(
&self,
pre_rent_state: &RentState,
do_support_realloc: bool,
prevent_crediting_accounts_that_end_rent_paying: bool,
) -> bool {
if let Self::RentPaying(post_data_size) = self {
if let Self::RentPaying(pre_data_size) = pre_rent_state {
if do_support_realloc {
post_data_size == pre_data_size // Cannot be RentPaying if resized
} else {
true // RentPaying can continue to be RentPaying
match self {
Self::Uninitialized | Self::RentExempt => true,
Self::RentPaying {
data_size: post_data_size,
lamports: post_lamports,
} => {
match pre_rent_state {
Self::Uninitialized | Self::RentExempt => false,
Self::RentPaying {
data_size: pre_data_size,
lamports: pre_lamports,
} => {
// Cannot remain RentPaying if resized
if do_support_realloc && post_data_size != pre_data_size {
false
} else if prevent_crediting_accounts_that_end_rent_paying {
// Cannot remain RentPaying if credited
post_lamports <= pre_lamports
} else {
true
}
}
}
} else {
false // Only RentPaying can continue to be RentPaying
}
} else {
true // Post not-RentPaying always ok
}
}
}

pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) {
match (pre_rent_state, post_rent_state) {
(&RentState::Uninitialized, &RentState::RentPaying(_)) => {
(&RentState::Uninitialized, &RentState::RentPaying { .. }) => {
inc_new_counter_info!("rent_paying_err-new_account", 1);
}
(&RentState::RentPaying(_), &RentState::RentPaying(_)) => {
(&RentState::RentPaying { .. }, &RentState::RentPaying { .. }) => {
inc_new_counter_info!("rent_paying_ok-legacy", 1);
}
(_, &RentState::RentPaying(_)) => {
(_, &RentState::RentPaying { .. }) => {
inc_new_counter_info!("rent_paying_err-other", 1);
}
_ => {}
Expand All @@ -74,6 +92,7 @@ pub(crate) fn check_rent_state(
index: usize,
do_support_realloc: bool,
include_account_index_in_err: bool,
prevent_crediting_accounts_that_end_rent_paying: bool,
) -> Result<()> {
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
let expect_msg = "account must exist at TransactionContext index if rent-states are Some";
Expand All @@ -89,6 +108,7 @@ pub(crate) fn check_rent_state(
.borrow(),
do_support_realloc,
include_account_index_in_err.then(|| index),
prevent_crediting_accounts_that_end_rent_paying,
)?;
}
Ok(())
Expand All @@ -101,10 +121,15 @@ pub(crate) fn check_rent_state_with_account(
account_state: &AccountSharedData,
do_support_realloc: bool,
account_index: Option<usize>,
prevent_crediting_accounts_that_end_rent_paying: bool,
) -> Result<()> {
submit_rent_state_metrics(pre_rent_state, post_rent_state);
if !solana_sdk::incinerator::check_id(address)
&& !post_rent_state.transition_allowed_from(pre_rent_state, do_support_realloc)
&& !post_rent_state.transition_allowed_from(
pre_rent_state,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying,
)
{
debug!(
"Account {} not rent exempt, state {:?}",
Expand Down Expand Up @@ -163,7 +188,10 @@ mod tests {
);
assert_eq!(
RentState::from_account(&rent_paying_account, &rent),
RentState::RentPaying(account_data_size)
RentState::RentPaying {
data_size: account_data_size,
lamports: rent_paying_account.lamports(),
}
);
assert_eq!(
RentState::from_account(&rent_exempt_account, &rent),
Expand All @@ -173,21 +201,117 @@ mod tests {

#[test]
fn test_transition_allowed_from() {
check_transition_allowed_from(
/*prevent_crediting_accounts_that_end_rent_paying:*/ false,
);
check_transition_allowed_from(
/*prevent_crediting_accounts_that_end_rent_paying:*/ true,
);
}

fn check_transition_allowed_from(prevent_crediting_accounts_that_end_rent_paying: bool) {
let do_support_realloc = true;
let post_rent_state = RentState::Uninitialized;
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));
assert!(post_rent_state.transition_allowed_from(
&RentState::Uninitialized,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(post_rent_state.transition_allowed_from(
&RentState::RentExempt,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 0,
lamports: 1,
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));

let post_rent_state = RentState::RentExempt;
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));

let post_rent_state = RentState::RentPaying(2);
assert!(!post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(3), true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(1), true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(2), true));
assert!(post_rent_state.transition_allowed_from(
&RentState::Uninitialized,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(post_rent_state.transition_allowed_from(
&RentState::RentExempt,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 0,
lamports: 1,
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
let post_rent_state = RentState::RentPaying {
data_size: 2,
lamports: 5,
};
assert!(!post_rent_state.transition_allowed_from(
&RentState::Uninitialized,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(!post_rent_state.transition_allowed_from(
&RentState::RentExempt,
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(!post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 3,
lamports: 5
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
assert!(!post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 1,
lamports: 5
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
// Transition is always allowed if there is no account data resize or
// change in account's lamports.
assert!(post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 2,
lamports: 5
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
// Transition is always allowed if there is no account data resize and
// account's lamports is reduced.
assert!(post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 2,
lamports: 7
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
));
// Once the feature is activated, transition is not allowed if the
// account is credited with more lamports and remains rent-paying.
assert_eq!(
post_rent_state.transition_allowed_from(
&RentState::RentPaying {
data_size: 2,
lamports: 3
},
do_support_realloc,
prevent_crediting_accounts_that_end_rent_paying
),
!prevent_crediting_accounts_that_end_rent_paying
);
}
}
2 changes: 2 additions & 0 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ impl Accounts {
feature_set
.is_active(&feature_set::include_account_index_in_rent_error::ID)
.then(|| payer_index),
feature_set
.is_active(&feature_set::prevent_crediting_accounts_that_end_rent_paying::id()),
);
// Feature gate only wraps the actual error return so that the metrics and debug
// logging generated by `check_rent_state_with_account()` can be examined before
Expand Down
4 changes: 4 additions & 0 deletions runtime/src/bank/transaction_account_state_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ impl Bank {
let include_account_index_in_err = self
.feature_set
.is_active(&feature_set::include_account_index_in_rent_error::id());
let prevent_crediting_accounts_that_end_rent_paying = self
.feature_set
.is_active(&feature_set::prevent_crediting_accounts_that_end_rent_paying::id());
for (i, (pre_state_info, post_state_info)) in
pre_state_infos.iter().zip(post_state_infos).enumerate()
{
Expand All @@ -74,6 +77,7 @@ impl Bank {
i,
do_support_realloc,
include_account_index_in_err,
prevent_crediting_accounts_that_end_rent_paying,
) {
// Feature gate only wraps the actual error return so that the metrics and debug
// logging generated by `check_rent_state()` can be examined before feature
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/feature_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,10 @@ pub mod preserve_rent_epoch_for_rent_exempt_accounts {
solana_sdk::declare_id!("HH3MUYReL2BvqqA3oEcAa7txju5GY6G4nxJ51zvsEjEZ");
}

pub mod prevent_crediting_accounts_that_end_rent_paying {
solana_sdk::declare_id!("812kqX67odAp5NFwM8D2N24cku7WTm9CHUTFUXaDkWPn");
}

lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
Expand Down Expand Up @@ -503,6 +507,7 @@ lazy_static! {
(vote_authorize_with_seed::id(), "An instruction you can use to change a vote accounts authority when the current authority is a derived key #25860"),
(cap_accounts_data_size_per_block::id(), "cap the accounts data size per block #25517"),
(preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"),
(prevent_crediting_accounts_that_end_rent_paying::id(), "prevent crediting rent paying accounts #26606"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()
Expand Down