diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5c63f05e693b17..2bdb21e6d3573b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -43,7 +43,6 @@ use { builtins::{BuiltinPrototype, BUILTINS}, epoch_rewards_hasher::hash_rewards_into_partitions, epoch_stakes::{EpochStakes, NodeVoteAccounts}, - inline_feature_gate_program, runtime_config::RuntimeConfig, serde_snapshot::BankIncrementalSnapshotPersistence, snapshot_hash::SnapshotHash, @@ -218,7 +217,6 @@ pub mod bank_hash_details; mod builtin_programs; pub mod epoch_accounts_hash_utils; mod metrics; -mod replace_account; mod serde_snapshot; mod sysvar_cache; #[cfg(test)] @@ -8085,24 +8083,6 @@ impl Bank { if new_feature_activations.contains(&feature_set::update_hashes_per_tick6::id()) { self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK6); } - - if new_feature_activations.contains(&feature_set::programify_feature_gate_program::id()) { - let datapoint_name = "bank-progamify_feature_gate_program"; - if let Err(e) = replace_account::replace_empty_account_with_upgradeable_program( - self, - &feature::id(), - &inline_feature_gate_program::noop_program::id(), - datapoint_name, - ) { - warn!( - "{}: Failed to replace empty account {} with upgradeable program: {}", - datapoint_name, - feature::id(), - e - ); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - } - } } fn apply_updated_hashes_per_tick(&mut self, hashes_per_tick: u64) { @@ -8244,6 +8224,42 @@ impl Bank { } } + /// Use to replace programs by feature activation + #[allow(dead_code)] + fn replace_program_account( + &mut self, + old_address: &Pubkey, + new_address: &Pubkey, + datapoint_name: &'static str, + ) { + if let Some(old_account) = self.get_account_with_fixed_root(old_address) { + if let Some(new_account) = self.get_account_with_fixed_root(new_address) { + datapoint_info!(datapoint_name, ("slot", self.slot, i64)); + + // Burn lamports in the old account + self.capitalization + .fetch_sub(old_account.lamports(), Relaxed); + + // Transfer new account to old account + self.store_account(old_address, &new_account); + + // Clear new account + self.store_account(new_address, &AccountSharedData::default()); + + // Unload a program from the bank's cache + self.loaded_programs_cache + .write() + .unwrap() + .remove_programs([*old_address].into_iter()); + + self.calculate_and_update_accounts_data_size_delta_off_chain( + old_account.data().len(), + new_account.data().len(), + ); + } + } + } + /// Get all the accounts for this bank and calculate stats pub fn get_total_accounts_stats(&self) -> ScanResult { let accounts = self.get_all_accounts()?; diff --git a/runtime/src/bank/replace_account.rs b/runtime/src/bank/replace_account.rs deleted file mode 100644 index 8d650aeebe7e87..00000000000000 --- a/runtime/src/bank/replace_account.rs +++ /dev/null @@ -1,191 +0,0 @@ -use { - super::Bank, - log::*, - solana_accounts_db::accounts_index::ZeroLamport, - solana_sdk::{ - account::{Account, AccountSharedData, ReadableAccount}, - bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - pubkey::Pubkey, - }, - std::sync::atomic::Ordering::Relaxed, - thiserror::Error, -}; - -/// Errors returned by `replace_account` methods -#[derive(Debug, Error)] -pub enum ReplaceAccountError { - /// Account not found - #[error("Account not found: {0:?}")] - AccountNotFound(Pubkey), - /// Account exists - #[error("Account exists: {0:?}")] - AccountExists(Pubkey), - #[error("Bincode Error: {0}")] - BincodeError(#[from] bincode::Error), - /// Not an upgradeable program - #[error("Not an upgradeable program")] - NotAnUpgradeableProgram, -} - -/// Moves one account in place of another -/// `source`: the account to replace with -/// `destination`: the account to be replaced -fn move_account( - bank: &Bank, - source_address: &Pubkey, - source_account: &V, - destination_address: &Pubkey, - destination_account: Option<&U>, -) where - U: ReadableAccount + Sync + ZeroLamport, - V: ReadableAccount + Sync + ZeroLamport, -{ - let (destination_lamports, destination_len) = match destination_account { - Some(destination_account) => ( - destination_account.lamports(), - destination_account.data().len(), - ), - None => (0, 0), - }; - - // Burn lamports in the destination account - bank.capitalization.fetch_sub(destination_lamports, Relaxed); - - // Transfer source account to destination account - bank.store_account(destination_address, source_account); - - // Clear source account - bank.store_account(source_address, &AccountSharedData::default()); - - bank.calculate_and_update_accounts_data_size_delta_off_chain( - destination_len, - source_account.data().len(), - ); -} - -/// Use to replace non-upgradeable programs by feature activation -/// `source`: the non-upgradeable program account to replace with -/// `destination`: the non-upgradeable program account to be replaced -#[allow(dead_code)] -pub(crate) fn replace_non_upgradeable_program_account( - bank: &Bank, - source_address: &Pubkey, - destination_address: &Pubkey, - datapoint_name: &'static str, -) -> Result<(), ReplaceAccountError> { - let destination_account = bank - .get_account_with_fixed_root(destination_address) - .ok_or(ReplaceAccountError::AccountNotFound(*destination_address))?; - let source_account = bank - .get_account_with_fixed_root(source_address) - .ok_or(ReplaceAccountError::AccountNotFound(*source_address))?; - - datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); - - move_account( - bank, - source_address, - &source_account, - destination_address, - Some(&destination_account), - ); - - // Unload a program from the bank's cache - bank.loaded_programs_cache - .write() - .unwrap() - .remove_programs([*destination_address].into_iter()); - - Ok(()) -} - -/// Use to replace an empty account with a program by feature activation -/// Note: The upgradeable program should have both: -/// - Program account -/// - Program data account -/// `source`: the upgradeable program account to replace with -/// `destination`: the empty account to be replaced -pub(crate) fn replace_empty_account_with_upgradeable_program( - bank: &Bank, - source_address: &Pubkey, - destination_address: &Pubkey, - datapoint_name: &'static str, -) -> Result<(), ReplaceAccountError> { - // Must be attempting to replace an empty account with a program - // account _and_ data account - let source_account = bank - .get_account_with_fixed_root(source_address) - .ok_or(ReplaceAccountError::AccountNotFound(*source_address))?; - - let (destination_data_address, _) = Pubkey::find_program_address( - &[destination_address.as_ref()], - &bpf_loader_upgradeable::id(), - ); - let (source_data_address, _) = - Pubkey::find_program_address(&[source_address.as_ref()], &bpf_loader_upgradeable::id()); - - // Make sure the data within the source account is the PDA of its - // data account. This also means it has at least the necessary - // lamports for rent. - let source_state = bincode::deserialize::(source_account.data())?; - if !matches!(source_state, UpgradeableLoaderState::Program { .. }) { - return Err(ReplaceAccountError::NotAnUpgradeableProgram); - } - - let source_data_account = bank - .get_account_with_fixed_root(&source_data_address) - .ok_or(ReplaceAccountError::AccountNotFound(source_data_address))?; - - // Make sure the destination account is empty - // We aren't going to check that there isn't a data account at - // the known program-derived address (ie. `destination_data_address`), - // because if it exists, it will be overwritten - if bank - .get_account_with_fixed_root(destination_address) - .is_some() - { - return Err(ReplaceAccountError::AccountExists(*destination_address)); - } - let state = UpgradeableLoaderState::Program { - programdata_address: destination_data_address, - }; - let data = bincode::serialize(&state)?; - let lamports = bank.get_minimum_balance_for_rent_exemption(data.len()); - let created_program_account = Account { - lamports, - data, - owner: bpf_loader_upgradeable::id(), - executable: true, - rent_epoch: source_account.rent_epoch(), - }; - - datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); - let change_in_capitalization = source_account.lamports().saturating_sub(lamports); - - // Replace the destination data account with the source one - // If the destination data account does not exist, it will be created - // If it does exist, it will be overwritten - move_account( - bank, - &source_data_address, - &source_data_account, - &destination_data_address, - bank.get_account_with_fixed_root(&destination_data_address) - .as_ref(), - ); - - // Write the source data account's PDA into the destination program account - move_account( - bank, - source_address, - &created_program_account, - destination_address, - None::<&AccountSharedData>, - ); - - // Any remaining lamports in the source program account are burnt - bank.capitalization - .fetch_sub(change_in_capitalization, Relaxed); - - Ok(()) -} diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 311b928a2995bd..2f1c0e0aee45f7 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -10,10 +10,6 @@ use { accounts_background_service::{ AbsRequestSender, PrunedBanksRequestHandler, SendDroppedBankCallback, }, - bank::replace_account::{ - replace_empty_account_with_upgradeable_program, - replace_non_upgradeable_program_account, ReplaceAccountError, - }, bank_client::BankClient, bank_forks::BankForks, epoch_rewards_hasher::hash_rewards_into_partitions, @@ -8014,403 +8010,42 @@ fn test_compute_active_feature_set() { assert!(feature_set.is_active(&test_feature)); } -fn test_program_replace_set_up_account( - bank: &Bank, - pubkey: &Pubkey, - lamports: u64, - state: &T, - owner: &Pubkey, - executable: bool, -) -> AccountSharedData { - let data_len = bincode::serialized_size(state).unwrap() as usize; - let mut account = AccountSharedData::from(Account { - lamports, - owner: *owner, - executable, - data: vec![0u8; data_len], - ..Account::default() - }); - account.serialize_data(state).unwrap(); - bank.store_account_and_update_capitalization(pubkey, &account); - assert_eq!(bank.get_balance(pubkey), lamports); - account -} - #[test] -fn test_replace_non_upgradeable_program_account() { - // Non-upgradeable program - // - Destination: [Destination program data] - // - Source: [*Source program data] - // - // Should replace the destination program account with the source program account: - // - Destination: [*Source program data] - let bpf_id = bpf_loader::id(); - let bank = create_simple_test_bank(0); - - let destination = Pubkey::new_unique(); - let destination_state = vec![0u8; 4]; - let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); - test_program_replace_set_up_account( - &bank, - &destination, - destination_lamports, - &destination_state, - &bpf_id, - true, - ); - - let source = Pubkey::new_unique(); - let source_state = vec![6; 30]; - let source_lamports = bank.get_minimum_balance_for_rent_exemption(source_state.len()); - let check_source_account = test_program_replace_set_up_account( - &bank, - &source, - source_lamports, - &source_state, - &bpf_id, - true, - ); - let check_data_account_data = check_source_account.data().to_vec(); - - let original_capitalization = bank.capitalization(); - - replace_non_upgradeable_program_account( - &bank, - &source, - &destination, - "bank-apply_program_replacement", - ) - .unwrap(); - - // Destination program account balance is now the source program account's balance - assert_eq!(bank.get_balance(&destination), source_lamports); - - // Source program account is now empty - assert_eq!(bank.get_balance(&source), 0); - - // Destination program account now holds the source program data, ie: - // - Destination: [*Source program data] - let destination_account = bank.get_account(&destination).unwrap(); - assert_eq!(destination_account.data(), &check_data_account_data); - - // Ownership & executable match the source program account - assert_eq!(destination_account.owner(), &bpf_id); - assert!(destination_account.executable()); - - // The destination account's original lamports balance was burnt - assert_eq!( - bank.capitalization(), - original_capitalization - destination_lamports - ); -} - -#[test_case( - Pubkey::new_unique(), - None; - "Empty destination account _without_ corresponding data account" -)] -#[test_case( - Pubkey::new_unique(), - Some(vec![4; 40]); - "Empty destination account _with_ corresponding data account" -)] -#[test_case( - feature::id(), // `Feature11111111` - None; - "Native destination account _without_ corresponding data account" -)] -#[test_case( - feature::id(), // `Feature11111111` - Some(vec![4; 40]); - "Native destination account _with_ corresponding data account" -)] -fn test_replace_empty_account_with_upgradeable_program_success( - destination: Pubkey, - maybe_destination_data_state: Option>, // Inner data of the destination program _data_ account -) { - // Ensures a program account and data account are created when replacing an - // empty account, ie: - // - Destination: PDA(DestinationData) - // - DestinationData: [Destination program data] - // - // If the destination data account exists, it will be overwritten - let bpf_upgradeable_id = bpf_loader_upgradeable::id(); - let bank = create_simple_test_bank(0); - - // Create the test source accounts, one for program and one for data - let source = Pubkey::new_unique(); - let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); - let source_state = UpgradeableLoaderState::Program { - programdata_address: source_data, - }; - let source_lamports = - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); - let source_data_state = vec![6; 30]; - let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); - test_program_replace_set_up_account( - &bank, - &source, - source_lamports, - &source_state, - &bpf_upgradeable_id, - true, - ); - let check_source_data_account = test_program_replace_set_up_account( - &bank, - &source_data, - source_data_lamports, - &source_data_state, - &bpf_upgradeable_id, - false, - ); - let check_data_account_data = check_source_data_account.data().to_vec(); - - // Derive the well-known PDA address for the destination data account - let (destination_data, _) = - Pubkey::find_program_address(&[destination.as_ref()], &bpf_upgradeable_id); - - // Determine the lamports that will be burnt after the replacement - let burnt_after_rent = if let Some(destination_data_state) = maybe_destination_data_state { - // Create the data account if necessary - let destination_data_lamports = - bank.get_minimum_balance_for_rent_exemption(destination_data_state.len()); - test_program_replace_set_up_account( - &bank, - &destination_data, - destination_data_lamports, - &destination_data_state, - &bpf_upgradeable_id, - false, - ); - destination_data_lamports + source_lamports - - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) - } else { - source_lamports - - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) - }; - - let original_capitalization = bank.capitalization(); - - // Do the replacement - replace_empty_account_with_upgradeable_program( - &bank, - &source, - &destination, - "bank-apply_empty_account_replacement_for_program", - ) - .unwrap(); - - // Destination program account was created and funded to pay for minimum rent - // for the PDA - assert_eq!( - bank.get_balance(&destination), - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()), - ); - - // Destination data account was created, now holds the source data account's balance - assert_eq!(bank.get_balance(&destination_data), source_data_lamports); +fn test_program_replacement() { + let mut bank = create_simple_test_bank(0); - // Source program accounts are now empty - assert_eq!(bank.get_balance(&source), 0); - assert_eq!(bank.get_balance(&source_data), 0); - - // Destination program account holds the PDA, ie: - // - Destination: PDA(DestinationData) - let destination_account = bank.get_account(&destination).unwrap(); - assert_eq!( - destination_account.data(), - &bincode::serialize(&UpgradeableLoaderState::Program { - programdata_address: destination_data - }) - .unwrap(), - ); - - // Destination data account holds the source data, ie: - // - DestinationData: [*Source program data] - let destination_data_account = bank.get_account(&destination_data).unwrap(); - assert_eq!(destination_data_account.data(), &check_data_account_data); - - // Ownership & executable match the source program accounts - assert_eq!(destination_account.owner(), &bpf_upgradeable_id); - assert!(destination_account.executable()); - assert_eq!(destination_data_account.owner(), &bpf_upgradeable_id); - assert!(!destination_data_account.executable()); - - // The remaining lamports from both program accounts minus the rent-exempt - // minimum were burnt - assert_eq!( - bank.capitalization(), - original_capitalization - burnt_after_rent - ); -} - -#[test_case( - None; - "Existing destination account _without_ corresponding data account" -)] -#[test_case( - Some(vec![4; 40]); - "Existing destination account _with_ corresponding data account" -)] -fn test_replace_empty_account_with_upgradeable_program_fail_when_account_exists( - maybe_destination_data_state: Option>, // Inner data of the destination program _data_ account -) { - // Should not be allowed to execute replacement - let bpf_upgradeable_id = bpf_loader_upgradeable::id(); - let bank = create_simple_test_bank(0); - - // Create the test destination account with some arbitrary data and lamports balance - let destination = Pubkey::new_unique(); - let destination_state = vec![0, 0, 0, 0]; // Arbitrary bytes, doesn't matter - let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); - let destination_account = test_program_replace_set_up_account( - &bank, - &destination, - destination_lamports, - &destination_state, - &bpf_upgradeable_id, - true, - ); - - // Create the test source accounts, one for program and one for data - let source = Pubkey::new_unique(); - let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); - let source_state = UpgradeableLoaderState::Program { - programdata_address: source_data, - }; - let source_lamports = - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); - let source_data_state = vec![6; 30]; - let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); - let source_account = test_program_replace_set_up_account( - &bank, - &source, - source_lamports, - &source_state, - &bpf_upgradeable_id, - true, - ); - let source_data_account = test_program_replace_set_up_account( - &bank, - &source_data, - source_data_lamports, - &source_data_state, - &bpf_upgradeable_id, - false, + // Setup original program account + let old_address = Pubkey::new_unique(); + let new_address = Pubkey::new_unique(); + bank.store_account_and_update_capitalization( + &old_address, + &AccountSharedData::from(Account { + lamports: 100, + ..Account::default() + }), ); + assert_eq!(bank.get_balance(&old_address), 100); - // Derive the well-known PDA address for the destination data account - let (destination_data, _) = - Pubkey::find_program_address(&[destination.as_ref()], &bpf_upgradeable_id); - - // Create the data account if necessary - let destination_data_account = - if let Some(destination_data_state) = maybe_destination_data_state { - let destination_data_lamports = - bank.get_minimum_balance_for_rent_exemption(destination_data_state.len()); - let destination_data_account = test_program_replace_set_up_account( - &bank, - &destination_data, - destination_data_lamports, - &destination_data_state, - &bpf_upgradeable_id, - false, - ); - Some(destination_data_account) - } else { - None - }; + // Setup new program account + let new_program_account = AccountSharedData::from(Account { + lamports: 123, + ..Account::default() + }); + bank.store_account_and_update_capitalization(&new_address, &new_program_account); + assert_eq!(bank.get_balance(&new_address), 123); let original_capitalization = bank.capitalization(); - // Attempt the replacement - assert_matches!( - replace_empty_account_with_upgradeable_program( - &bank, - &source, - &destination, - "bank-apply_empty_account_replacement_for_program", - ) - .unwrap_err(), - ReplaceAccountError::AccountExists(..) - ); - - // Everything should be unchanged - assert_eq!(bank.get_account(&destination).unwrap(), destination_account); - if let Some(destination_data_account) = destination_data_account { - assert_eq!( - bank.get_account(&destination_data).unwrap(), - destination_data_account - ); - } - assert_eq!(bank.get_account(&source).unwrap(), source_account); - assert_eq!(bank.get_account(&source_data).unwrap(), source_data_account); - assert_eq!(bank.capitalization(), original_capitalization); -} - -#[test] -fn test_replace_empty_account_with_upgradeable_program_fail_when_not_upgradeable_program() { - // Should not be allowed to execute replacement - let bpf_upgradeable_id = bpf_loader_upgradeable::id(); - let bank = create_simple_test_bank(0); - - // Create the test destination account with some arbitrary data and lamports balance - let destination = Pubkey::new_unique(); - let destination_state = vec![0, 0, 0, 0]; // Arbitrary bytes, doesn't matter - let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); - let destination_account = test_program_replace_set_up_account( - &bank, - &destination, - destination_lamports, - &destination_state, - &bpf_upgradeable_id, - true, - ); - - // Create the test source accounts, one for program and one for data - let source = Pubkey::new_unique(); - let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); - let source_state = [0, 0, 0, 0]; // Arbitrary bytes, NOT an upgradeable program - let source_lamports = - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); - let source_data_state = vec![6; 30]; - let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); - let source_account = test_program_replace_set_up_account( - &bank, - &source, - source_lamports, - &source_state, - &bpf_upgradeable_id, - true, - ); - let source_data_account = test_program_replace_set_up_account( - &bank, - &source_data, - source_data_lamports, - &source_data_state, - &bpf_upgradeable_id, - false, - ); + bank.replace_program_account(&old_address, &new_address, "bank-apply_program_replacement"); - let original_capitalization = bank.capitalization(); + // New program account is now empty + assert_eq!(bank.get_balance(&new_address), 0); - // Attempt the replacement - assert_matches!( - replace_empty_account_with_upgradeable_program( - &bank, - &source, - &destination, - "bank-apply_empty_account_replacement_for_program", - ) - .unwrap_err(), - ReplaceAccountError::NotAnUpgradeableProgram - ); + // Old program account holds the new program account + assert_eq!(bank.get_account(&old_address), Some(new_program_account)); - // Everything should be unchanged - assert_eq!(bank.get_account(&destination).unwrap(), destination_account); - assert_eq!(bank.get_account(&source).unwrap(), source_account); - assert_eq!(bank.get_account(&source_data).unwrap(), source_data_account); - assert_eq!(bank.capitalization(), original_capitalization); + // Lamports in the old token account were burnt + assert_eq!(bank.capitalization(), original_capitalization - 100); } fn min_rent_exempt_balance_for_sysvars(bank: &Bank, sysvar_ids: &[Pubkey]) -> u64 { diff --git a/runtime/src/inline_feature_gate_program.rs b/runtime/src/inline_feature_gate_program.rs deleted file mode 100644 index a2c647bbda22a0..00000000000000 --- a/runtime/src/inline_feature_gate_program.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Contains replacement program IDs for the feature gate program - -pub(crate) mod noop_program { - solana_sdk::declare_id!("37Yr1mVPdfUuy6oC2yPjWtg8xyyVi33TYYqyNQocsAkT"); -} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1bbd479848e987..e6ba2b1bd8969b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,7 +14,6 @@ pub mod commitment; mod epoch_rewards_hasher; pub mod epoch_stakes; pub mod genesis_utils; -pub mod inline_feature_gate_program; pub mod inline_spl_associated_token_account; pub mod installed_scheduler_pool; pub mod loader_utils; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 8682836c2ba247..376880e6327d6a 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -700,10 +700,6 @@ pub mod better_error_codes_for_tx_lamport_check { solana_sdk::declare_id!("Ffswd3egL3tccB6Rv3XY6oqfdzn913vUcjCSnpvCKpfx"); } -pub mod programify_feature_gate_program { - solana_sdk::declare_id!("8GdovDzVwWU5edz2G697bbB7GZjrUc6aQZLWyNNAtHdg"); -} - pub mod update_hashes_per_tick2 { solana_sdk::declare_id!("EWme9uFqfy1ikK1jhJs8fM5hxWnK336QJpbscNtizkTU"); } @@ -894,7 +890,6 @@ lazy_static! { (require_rent_exempt_split_destination::id(), "Require stake split destination account to be rent exempt"), (better_error_codes_for_tx_lamport_check::id(), "better error codes for tx lamport check #33353"), (enable_alt_bn128_compression_syscall::id(), "add alt_bn128 compression syscalls"), - (programify_feature_gate_program::id(), "move feature gate activation logic to an on-chain program #32783"), (update_hashes_per_tick2::id(), "Update desired hashes per tick to 2.8M"), (update_hashes_per_tick3::id(), "Update desired hashes per tick to 4.4M"), (update_hashes_per_tick4::id(), "Update desired hashes per tick to 7.6M"),