Prevent Rent-reward recipients from ending up RentPaying (#30130)

This commit is contained in:
Tyera 2023-02-06 12:16:36 -07:00 committed by GitHub
parent 01f0dcdad4
commit a14473eb54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 215 additions and 2 deletions

View File

@ -39,6 +39,7 @@ pub use solana_sdk::reward_type::RewardType;
use {
crate::{
account_overrides::AccountOverrides,
account_rent_state::RentState,
accounts::{
AccountAddressFilter, Accounts, LoadedTransaction, PubkeyAccountSlot,
TransactionLoadResult,
@ -5032,8 +5033,27 @@ impl Bank {
let mut account = self
.get_account_with_fixed_root(&pubkey)
.unwrap_or_default();
if account.checked_add_lamports(rent_to_be_paid).is_err() {
// overflow adding lamports
let rent = self.rent_collector().rent;
let recipient_pre_rent_state = RentState::from_account(&account, &rent);
let distribution = account.checked_add_lamports(rent_to_be_paid);
let recipient_post_rent_state = RentState::from_account(&account, &rent);
let rent_state_transition_allowed = recipient_post_rent_state
.transition_allowed_from(&recipient_pre_rent_state);
if !rent_state_transition_allowed {
warn!(
"Rent distribution of {rent_to_be_paid} to {pubkey} results in \
invalid RentState: {recipient_post_rent_state:?}"
);
inc_new_counter_warn!(
"rent-distribution-rent-paying",
rent_to_be_paid as usize
);
}
if distribution.is_err()
|| (self.prevent_rent_paying_rent_recipients()
&& !rent_state_transition_allowed)
{
// overflow adding lamports or resulting account is not rent-exempt
self.capitalization.fetch_sub(rent_to_be_paid, Relaxed);
error!(
"Burned {} rent lamports instead of sending to {}",
@ -7363,6 +7383,11 @@ impl Bank {
.is_active(&feature_set::no_overflow_rent_distribution::id())
}
pub fn prevent_rent_paying_rent_recipients(&self) -> bool {
self.feature_set
.is_active(&feature_set::prevent_rent_paying_rent_recipients::id())
}
pub fn versioned_tx_message_enabled(&self) -> bool {
self.feature_set
.is_active(&feature_set::versioned_tx_message_enabled::id())

View File

@ -1115,6 +1115,189 @@ fn test_distribute_rent_to_validators_overflow() {
}
}
#[test]
fn test_distribute_rent_to_validators_rent_paying() {
solana_logger::setup();
const RENT_PER_VALIDATOR: u64 = 55;
const TOTAL_RENT: u64 = RENT_PER_VALIDATOR * 4;
let empty_validator = ValidatorVoteKeypairs::new_rand();
let rent_paying_validator = ValidatorVoteKeypairs::new_rand();
let becomes_rent_exempt_validator = ValidatorVoteKeypairs::new_rand();
let rent_exempt_validator = ValidatorVoteKeypairs::new_rand();
let keypairs = vec![
&empty_validator,
&rent_paying_validator,
&becomes_rent_exempt_validator,
&rent_exempt_validator,
];
let genesis_config_info = create_genesis_config_with_vote_accounts(
sol_to_lamports(1000.),
&keypairs,
vec![sol_to_lamports(1000.); 4],
);
let mut genesis_config = genesis_config_info.genesis_config;
genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default
for deactivate_feature in [false, true] {
if deactivate_feature {
genesis_config
.accounts
.remove(&feature_set::prevent_rent_paying_rent_recipients::id())
.unwrap();
}
let bank = Bank::new_for_tests(&genesis_config);
let rent = bank.rent_collector().rent;
let rent_exempt_minimum = rent.minimum_balance(0);
// Make one validator have an empty identity account
let mut empty_validator_account = bank
.get_account_with_fixed_root(&empty_validator.node_keypair.pubkey())
.unwrap();
empty_validator_account.set_lamports(0);
bank.store_account(
&empty_validator.node_keypair.pubkey(),
&empty_validator_account,
);
// Make one validator almost rent-exempt, less RENT_PER_VALIDATOR
let mut becomes_rent_exempt_validator_account = bank
.get_account_with_fixed_root(&becomes_rent_exempt_validator.node_keypair.pubkey())
.unwrap();
becomes_rent_exempt_validator_account
.set_lamports(rent_exempt_minimum - RENT_PER_VALIDATOR);
bank.store_account(
&becomes_rent_exempt_validator.node_keypair.pubkey(),
&becomes_rent_exempt_validator_account,
);
// Make one validator rent-exempt
let mut rent_exempt_validator_account = bank
.get_account_with_fixed_root(&rent_exempt_validator.node_keypair.pubkey())
.unwrap();
rent_exempt_validator_account.set_lamports(rent_exempt_minimum);
bank.store_account(
&rent_exempt_validator.node_keypair.pubkey(),
&rent_exempt_validator_account,
);
let get_rent_state = |bank: &Bank, address: &Pubkey| -> RentState {
let account = bank
.get_account_with_fixed_root(address)
.unwrap_or_default();
RentState::from_account(&account, &rent)
};
// Assert starting RentStates
assert_eq!(
get_rent_state(&bank, &empty_validator.node_keypair.pubkey()),
RentState::Uninitialized
);
assert_eq!(
get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()),
RentState::RentPaying {
lamports: 42,
data_size: 0,
}
);
assert_eq!(
get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()),
RentState::RentPaying {
lamports: rent_exempt_minimum - RENT_PER_VALIDATOR,
data_size: 0,
}
);
assert_eq!(
get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()),
RentState::RentExempt
);
let old_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey());
let old_rent_paying_validator_lamports =
bank.get_balance(&rent_paying_validator.node_keypair.pubkey());
let old_becomes_rent_exempt_validator_lamports =
bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey());
let old_rent_exempt_validator_lamports =
bank.get_balance(&rent_exempt_validator.node_keypair.pubkey());
bank.distribute_rent_to_validators(&bank.vote_accounts(), TOTAL_RENT);
let new_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey());
let new_rent_paying_validator_lamports =
bank.get_balance(&rent_paying_validator.node_keypair.pubkey());
let new_becomes_rent_exempt_validator_lamports =
bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey());
let new_rent_exempt_validator_lamports =
bank.get_balance(&rent_exempt_validator.node_keypair.pubkey());
// Assert ending balances; rent should be withheld if test is active and ending RentState
// is RentPaying, ie. empty_validator and rent_paying_validator
assert_eq!(
if deactivate_feature {
old_empty_validator_lamports + RENT_PER_VALIDATOR
} else {
old_empty_validator_lamports
},
new_empty_validator_lamports
);
assert_eq!(
if deactivate_feature {
old_rent_paying_validator_lamports + RENT_PER_VALIDATOR
} else {
old_rent_paying_validator_lamports
},
new_rent_paying_validator_lamports
);
assert_eq!(
old_becomes_rent_exempt_validator_lamports + RENT_PER_VALIDATOR,
new_becomes_rent_exempt_validator_lamports
);
assert_eq!(
old_rent_exempt_validator_lamports + RENT_PER_VALIDATOR,
new_rent_exempt_validator_lamports
);
// Assert ending RentStates
assert_eq!(
if deactivate_feature {
RentState::RentPaying {
lamports: RENT_PER_VALIDATOR,
data_size: 0,
}
} else {
RentState::Uninitialized
},
get_rent_state(&bank, &empty_validator.node_keypair.pubkey()),
);
assert_eq!(
if deactivate_feature {
RentState::RentPaying {
lamports: old_rent_paying_validator_lamports + RENT_PER_VALIDATOR,
data_size: 0,
}
} else {
RentState::RentPaying {
lamports: old_rent_paying_validator_lamports,
data_size: 0,
}
},
get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()),
);
assert_eq!(
RentState::RentExempt,
get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()),
);
assert_eq!(
RentState::RentExempt,
get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()),
);
}
}
#[test]
fn test_rent_exempt_executable_account() {
let (mut genesis_config, mint_keypair) = create_genesis_config(100_000);

View File

@ -606,6 +606,10 @@ pub mod enable_request_heap_frame_ix {
solana_sdk::declare_id!("Hr1nUA9b7NJ6eChS26o7Vi8gYYDDwWD3YeBfzJkTbU86");
}
pub mod prevent_rent_paying_rent_recipients {
solana_sdk::declare_id!("Fab5oP3DmsLYCiQZXdjyqT3ukFFPrsmqhXU4WU1AWVVF");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -752,6 +756,7 @@ lazy_static! {
(cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to a limit #27839"),
(remove_congestion_multiplier_from_fee_calculation::id(), "Remove congestion multiplier from transaction fee calculation #29881"),
(enable_request_heap_frame_ix::id(), "Enable transaction to request heap frame using compute budget instruction #30076"),
(prevent_rent_paying_rent_recipients::id(), "prevent recipients of rent rewards from ending in rent-paying state #30???"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()