diff --git a/programs/sbf/c/src/invoked/invoked.c b/programs/sbf/c/src/invoked/invoked.c index 2aa13ec87aa24e..3798b170ddf0b1 100644 --- a/programs/sbf/c/src/invoked/invoked.c +++ b/programs/sbf/c/src/invoked/invoked.c @@ -45,7 +45,7 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(accounts[ARGUMENT_INDEX].data_len == 100); sol_assert(accounts[ARGUMENT_INDEX].is_signer); sol_assert(accounts[ARGUMENT_INDEX].is_writable); - sol_assert(accounts[ARGUMENT_INDEX].rent_epoch == 18446744073709551615); + sol_assert(accounts[ARGUMENT_INDEX].rent_epoch == 18446744073709551615ul); sol_assert(!accounts[ARGUMENT_INDEX].executable); for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) { sol_assert(accounts[ARGUMENT_INDEX].data[i] == i); @@ -57,7 +57,7 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data_len == 10); sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_signer); sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_writable); - sol_assert(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch == 18446744073709551615); + sol_assert(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch == 18446744073709551615ul); sol_assert(!accounts[INVOKED_ARGUMENT_INDEX].executable); sol_assert( @@ -66,7 +66,7 @@ extern uint64_t entrypoint(const uint8_t *input) { &sbf_loader_id)); sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_signer); sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_writable); - sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch == 18446744073709551615); + sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch == 18446744073709551615ul); sol_assert(accounts[INVOKED_PROGRAM_INDEX].executable); sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key, diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 7f38fc71c5ed16..01319d7b74bb76 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -282,6 +282,8 @@ impl Accounts { }; let mut accumulated_accounts_data_size: usize = 0; + let set_exempt_rent_epoch_max = + feature_set.is_active(&solana_sdk::feature_set::set_exempt_rent_epoch_max::id()); let mut accounts = account_keys .iter() .enumerate() @@ -316,6 +318,7 @@ impl Accounts { key, &mut account, self.accounts_db.filler_account_suffix.as_ref(), + set_exempt_rent_epoch_max, ) .rent_amount; (account, rent_due) @@ -1600,11 +1603,20 @@ mod tests { ) } + fn all_features_except(exclude: Option<&[Pubkey]>) -> FeatureSet { + let mut features = FeatureSet::all_enabled(); + if let Some(exclude) = exclude { + features.active.retain(|k, _v| !exclude.contains(k)); + } + features + } + fn load_accounts_with_fee( tx: Transaction, ka: &[TransactionAccount], lamports_per_signature: u64, error_counters: &mut TransactionErrorMetrics, + exclude_features: Option<&[Pubkey]>, ) -> Vec { load_accounts_with_fee_and_rent( tx, @@ -1612,7 +1624,7 @@ mod tests { lamports_per_signature, &RentCollector::default(), error_counters, - &FeatureSet::all_enabled(), + &all_features_except(exclude_features), &FeeStructure::default(), ) } @@ -1622,7 +1634,16 @@ mod tests { ka: &[TransactionAccount], error_counters: &mut TransactionErrorMetrics, ) -> Vec { - load_accounts_with_fee(tx, ka, 0, error_counters) + load_accounts_with_excluded_features(tx, ka, error_counters, None) + } + + fn load_accounts_with_excluded_features( + tx: Transaction, + ka: &[TransactionAccount], + error_counters: &mut TransactionErrorMetrics, + exclude_features: Option<&[Pubkey]>, + ) -> Vec { + load_accounts_with_fee(tx, ka, 0, error_counters, exclude_features) } #[test] @@ -1771,8 +1792,13 @@ mod tests { ); assert_eq!(fee, lamports_per_signature); - let loaded_accounts = - load_accounts_with_fee(tx, &accounts, lamports_per_signature, &mut error_counters); + let loaded_accounts = load_accounts_with_fee( + tx, + &accounts, + lamports_per_signature, + &mut error_counters, + None, + ); assert_eq!(error_counters.insufficient_funds, 1); assert_eq!(loaded_accounts.len(), 1); @@ -1852,7 +1878,9 @@ mod tests { lamports_per_signature, &rent_collector, &mut error_counters, - &FeatureSet::all_enabled(), + &all_features_except(Some(&[ + solana_sdk::feature_set::set_exempt_rent_epoch_max::id(), + ])), &FeeStructure::default(), ); assert_eq!(loaded_accounts.len(), 1); @@ -1919,7 +1947,12 @@ mod tests { instructions, ); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.account_not_found, 0); assert_eq!(loaded_accounts.len(), 1); @@ -2107,7 +2140,12 @@ mod tests { instructions, ); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.account_not_found, 0); assert_eq!(loaded_accounts.len(), 1); @@ -2338,7 +2376,12 @@ mod tests { instructions, ); let tx = Transaction::new(&[&keypair], message.clone(), Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2351,7 +2394,12 @@ mod tests { message.account_keys = vec![key0, key1, key2]; // revert key change message.header.num_readonly_unsigned_accounts = 2; // mark both executables as readonly let tx = Transaction::new(&[&keypair], message, Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2425,7 +2473,12 @@ mod tests { instructions, ); let tx = Transaction::new(&[&keypair], message.clone(), Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2437,7 +2490,12 @@ mod tests { // Solution 1: include bpf_loader_upgradeable account message.account_keys = vec![key0, key1, bpf_loader_upgradeable::id()]; let tx = Transaction::new(&[&keypair], message.clone(), Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2452,7 +2510,12 @@ mod tests { message.account_keys = vec![key0, key1, key2]; // revert key change message.header.num_readonly_unsigned_accounts = 2; // mark both executables as readonly let tx = Transaction::new(&[&keypair], message, Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2510,7 +2573,12 @@ mod tests { instructions, ); let tx = Transaction::new(&[&keypair], message.clone(), Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2530,8 +2598,12 @@ mod tests { ]; message.account_keys = vec![key0, key1, bpf_loader_upgradeable::id()]; let tx = Transaction::new(&[&keypair], message.clone(), Hash::default()); - let loaded_accounts = - load_accounts(tx, &accounts_with_upgradeable_loader, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts_with_upgradeable_loader, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); @@ -2546,7 +2618,12 @@ mod tests { message.account_keys = vec![key0, key1, key2]; // revert key change message.header.num_readonly_unsigned_accounts = 2; // extend readonly set to include programdata let tx = Transaction::new(&[&keypair], message, Hash::default()); - let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters); + let loaded_accounts = load_accounts_with_excluded_features( + tx, + &accounts, + &mut error_counters, + Some(&[solana_sdk::feature_set::set_exempt_rent_epoch_max::id()]), + ); assert_eq!(error_counters.invalid_writable_account, 1); assert_eq!(loaded_accounts.len(), 1); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index b64d4eb5a719c5..571d730aca5cf1 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5378,6 +5378,8 @@ impl Bank { pubkey, account, self.rc.accounts.accounts_db.filler_account_suffix.as_ref(), + self.feature_set + .is_active(&solana_sdk::feature_set::set_exempt_rent_epoch_max::id()), )); time_collecting_rent_us += measure.as_us(); @@ -8051,11 +8053,12 @@ pub(crate) mod tests { ancestors::Ancestors, bank_client::BankClient, genesis_utils::{ - self, activate_all_features, bootstrap_validator_stake_lamports, + self, activate_all_features, activate_feature, bootstrap_validator_stake_lamports, create_genesis_config_with_leader, create_genesis_config_with_vote_accounts, genesis_sysvar_and_builtin_program_lamports, GenesisConfigInfo, ValidatorVoteKeypairs, }, + rent_collector::TEST_SET_EXEMPT_RENT_EPOCH_MAX, rent_paying_accounts_by_partition::RentPayingAccountsByPartition, status_cache::MAX_CACHE_ENTRIES, }, @@ -8513,6 +8516,7 @@ pub(crate) mod tests { &keypairs[4].pubkey(), &mut account_copy, None, + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(expected_rent.rent_amount, too_few_lamports); assert_eq!(account_copy.lamports(), 0); @@ -9979,13 +9983,17 @@ pub(crate) mod tests { solana_logger::setup(); let (mut genesis_config, _mint_keypair) = create_genesis_config(1_000_000); - activate_all_features(&mut genesis_config); + for feature_id in FeatureSet::default().inactive { + if feature_id != solana_sdk::feature_set::set_exempt_rent_epoch_max::id() { + activate_feature(&mut genesis_config, feature_id); + } + } let zero_lamport_pubkey = solana_sdk::pubkey::new_rand(); let rent_due_pubkey = solana_sdk::pubkey::new_rand(); let rent_exempt_pubkey = solana_sdk::pubkey::new_rand(); - - let mut bank = Arc::new(Bank::new_for_tests(&genesis_config)); + let bank = Bank::new_for_tests(&genesis_config); + let mut bank = Arc::new(bank); let zero_lamports = 0; let little_lamports = 1234; let large_lamports = 123_456_789; @@ -20270,6 +20278,7 @@ pub(crate) mod tests { &keypair.pubkey(), &mut account, None, + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(info.account_data_len_reclaimed, data_size as u64); } diff --git a/runtime/src/rent_collector.rs b/runtime/src/rent_collector.rs index c44fbff1d9c52e..a6273b580fe2fd 100644 --- a/runtime/src/rent_collector.rs +++ b/runtime/src/rent_collector.rs @@ -32,15 +32,22 @@ impl Default for RentCollector { } } +/// When rent is collected from an exempt account, rent_epoch is set to this +/// value. The idea is to have a fixed, consistent value for rent_epoch for all accounts that do not collect rent. +/// This enables us to get rid of the field completely. +const RENT_EXEMPT_RENT_EPOCH: Epoch = Epoch::MAX; + +pub const TEST_SET_EXEMPT_RENT_EPOCH_MAX: bool = false; + /// when rent is collected for this account, this is the action to apply to the account #[derive(Debug)] enum RentResult { - /// maybe collect rent later, leave account alone - LeaveAloneNoRent, + /// this account will never have rent collected from it + Exempt, /// collect rent CollectRent { new_rent_epoch: Epoch, - rent_due: u64, // lamports + rent_due: u64, // lamports, could be 0 }, } @@ -125,9 +132,20 @@ impl RentCollector { address: &Pubkey, account: &mut AccountSharedData, filler_account_suffix: Option<&Pubkey>, + set_exempt_rent_epoch_max: bool, ) -> CollectedInfo { - match self.calculate_rent_result(address, account, filler_account_suffix) { - RentResult::LeaveAloneNoRent => CollectedInfo::default(), + match self.calculate_rent_result( + address, + account, + filler_account_suffix, + set_exempt_rent_epoch_max, + ) { + RentResult::Exempt => { + if set_exempt_rent_epoch_max { + account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + } + CollectedInfo::default() + } RentResult::CollectRent { new_rent_epoch, rent_due, @@ -158,16 +176,27 @@ impl RentCollector { address: &Pubkey, account: &impl ReadableAccount, filler_account_suffix: Option<&Pubkey>, + set_exempt_rent_epoch_max: bool, ) -> RentResult { if self.can_skip_rent_collection(address, account, filler_account_suffix) { - return RentResult::LeaveAloneNoRent; + return RentResult::Exempt; } match self.get_rent_due(account) { // Rent isn't collected for the next epoch. // Make sure to check exempt status again later in current epoch. - RentDue::Exempt => RentResult::LeaveAloneNoRent, + RentDue::Exempt => RentResult::Exempt, // Maybe collect rent later, leave account alone. - RentDue::Paying(0) => RentResult::LeaveAloneNoRent, + RentDue::Paying(0) => { + if set_exempt_rent_epoch_max { + RentResult::CollectRent { + new_rent_epoch: account.rent_epoch(), + rent_due: 0, + } + } else { + RentResult::Exempt + } + } + // Rent is collected for next epoch. RentDue::Paying(rent_due) => RentResult::CollectRent { new_rent_epoch: self.epoch + 1, @@ -239,7 +268,10 @@ mod tests { // initialize rent_epoch as created at this epoch account.set_rent_epoch(self.epoch); self.collect_from_existing_account( - address, account, /*filler_account_suffix:*/ None, + address, + account, + /*filler_account_suffix:*/ None, + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ) } } @@ -278,6 +310,7 @@ mod tests { &solana_sdk::pubkey::new_rand(), &mut existing_account, None, // filler_account_suffix + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert!(existing_account.lamports() < old_lamports); assert_eq!( @@ -311,6 +344,7 @@ mod tests { &pubkey, &mut account, None, // filler_account_suffix + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(account.lamports(), huge_lamports); assert_eq!(collected, CollectedInfo::default()); @@ -323,6 +357,7 @@ mod tests { &pubkey, &mut account, None, // filler_account_suffix + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(account.lamports(), tiny_lamports - collected.rent_amount); assert_ne!(collected, CollectedInfo::default()); @@ -346,6 +381,7 @@ mod tests { &pubkey, &mut account, None, // filler_account_suffix + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(account.lamports(), 0); assert_eq!(collected.rent_amount, 1); @@ -370,6 +406,7 @@ mod tests { &Pubkey::new_unique(), &mut account, None, // filler_account_suffix + TEST_SET_EXEMPT_RENT_EPOCH_MAX, ); assert_eq!(collected.rent_amount, account_lamports); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index ca2cec38dda9ab..f0f93ec316163a 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -498,6 +498,10 @@ pub mod account_hash_ignore_slot { solana_sdk::declare_id!("SVn36yVApPLYsa8koK3qUcy14zXDnqkNYWyUh1f4oK1"); } +pub mod set_exempt_rent_epoch_max { + solana_sdk::declare_id!("5wAGiy15X1Jb2hkHnPDCM8oB9V42VNA9ftNVFK84dEgv"); +} + pub mod relax_authority_signer_check_for_lookup_table_creation { solana_sdk::declare_id!("FKAcEvNgSY79RpqsPNUV5gDyumopH4cEHqUxyfm8b8Ap"); } @@ -662,6 +666,7 @@ lazy_static! { (enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"), (disable_rehash_for_rent_epoch::id(), "on accounts hash calculation, do not try to rehash accounts #28934"), (account_hash_ignore_slot::id(), "ignore slot when calculating an account hash #28420"), + (set_exempt_rent_epoch_max::id(), "set rent epoch to Epoch::MAX for rent-exempt accounts #28683"), (on_load_preserve_rent_epoch_for_rent_exempt_accounts::id(), "on bank load account, do not try to fix up rent_epoch #28541"), (prevent_crediting_accounts_that_end_rent_paying::id(), "prevent crediting rent paying accounts #26606"), (cap_bpf_program_instruction_accounts::id(), "enforce max number of accounts per bpf program instruction #26628"),