restricts rent-paying accounts lifetime extension (#26606)
https://github.com/solana-labs/solana/pull/22292 prevents rent paying accounts creation going forward. However a rent paying account can linger on for ever if it is continually topped up but stays below the rent-exempt minimum. This can prevent eliminating accounts-rewrites and the problematic rent_epoch field in accounts. Link to discord discussion: https://discord.com/channels/428295358100013066/943609352068145162/995202300001927219 This commit restricts rent-paying accounts lifetime extension by preventing increasing lamports on the account if the account stays below the rent-exempt minimum.
This commit is contained in:
parent
6887e1268e
commit
bf225bae73
|
@ -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,
|
||||
}
|
||||
|
@ -24,35 +26,58 @@ 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) -> bool {
|
||||
if let Self::RentPaying(post_data_size) = self {
|
||||
if let Self::RentPaying(pre_data_size) = pre_rent_state {
|
||||
post_data_size == pre_data_size // Cannot be RentPaying if resized
|
||||
} else {
|
||||
false // Only RentPaying can continue to be RentPaying
|
||||
pub(crate) fn transition_allowed_from(
|
||||
&self,
|
||||
pre_rent_state: &RentState,
|
||||
prevent_crediting_accounts_that_end_rent_paying: bool,
|
||||
) -> bool {
|
||||
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 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 {
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -65,6 +90,7 @@ pub(crate) fn check_rent_state(
|
|||
transaction_context: &TransactionContext,
|
||||
index: usize,
|
||||
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";
|
||||
|
@ -79,6 +105,7 @@ pub(crate) fn check_rent_state(
|
|||
.expect(expect_msg)
|
||||
.borrow(),
|
||||
include_account_index_in_err.then(|| index),
|
||||
prevent_crediting_accounts_that_end_rent_paying,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -90,10 +117,14 @@ pub(crate) fn check_rent_state_with_account(
|
|||
address: &Pubkey,
|
||||
account_state: &AccountSharedData,
|
||||
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)
|
||||
&& !post_rent_state.transition_allowed_from(
|
||||
pre_rent_state,
|
||||
prevent_crediting_accounts_that_end_rent_paying,
|
||||
)
|
||||
{
|
||||
debug!(
|
||||
"Account {} not rent exempt, state {:?}",
|
||||
|
@ -152,7 +183,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),
|
||||
|
@ -162,21 +196,103 @@ 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 post_rent_state = RentState::Uninitialized;
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0)));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::Uninitialized,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::RentExempt,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::RentPaying {
|
||||
data_size: 0,
|
||||
lamports: 1,
|
||||
},
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
|
||||
let post_rent_state = RentState::RentExempt;
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0)));
|
||||
|
||||
let post_rent_state = RentState::RentPaying(2);
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::Uninitialized));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentExempt));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(3)));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(1)));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(2)));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::Uninitialized,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::RentExempt,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(post_rent_state.transition_allowed_from(
|
||||
&RentState::RentPaying {
|
||||
data_size: 0,
|
||||
lamports: 1,
|
||||
},
|
||||
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,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(!post_rent_state.transition_allowed_from(
|
||||
&RentState::RentExempt,
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(!post_rent_state.transition_allowed_from(
|
||||
&RentState::RentPaying {
|
||||
data_size: 3,
|
||||
lamports: 5
|
||||
},
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
));
|
||||
assert!(!post_rent_state.transition_allowed_from(
|
||||
&RentState::RentPaying {
|
||||
data_size: 1,
|
||||
lamports: 5
|
||||
},
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
prevent_crediting_accounts_that_end_rent_paying
|
||||
),
|
||||
!prevent_crediting_accounts_that_end_rent_paying
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -441,6 +441,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()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,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()
|
||||
{
|
||||
|
@ -67,6 +70,7 @@ impl Bank {
|
|||
transaction_context,
|
||||
i,
|
||||
include_account_index_in_err,
|
||||
prevent_crediting_accounts_that_end_rent_paying,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -460,6 +460,10 @@ pub mod enable_early_verification_of_account_modifications {
|
|||
solana_sdk::declare_id!("7Vced912WrRnfjaiKRiNBcbuFw7RrnLv3E3z95Y4GTNc");
|
||||
}
|
||||
|
||||
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> = [
|
||||
|
@ -569,6 +573,7 @@ lazy_static! {
|
|||
(preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"),
|
||||
(enable_bpf_loader_extend_program_data_ix::id(), "enable bpf upgradeable loader ExtendProgramData instruction #25234"),
|
||||
(enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"),
|
||||
(prevent_crediting_accounts_that_end_rent_paying::id(), "prevent crediting rent paying accounts #26606"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
Loading…
Reference in New Issue