diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 0b4855ccb..d4ecb0d46 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -21,11 +21,12 @@ use { }, solana_runtime::bank::Bank, solana_sdk::{ - account::AccountSharedData, + account::{create_account_shared_data_for_test, AccountSharedData}, account_utils::StateMut, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, pubkey::Pubkey, slot_history::Slot, + sysvar, transaction_context::{IndexOfAccount, InstructionAccount}, }, std::{ @@ -510,13 +511,16 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { program_id, // ID of the loaded program. It can modify accounts with the same owner key AccountSharedData::new(0, 0, &loader_id), )); + transaction_accounts.push(( + sysvar::epoch_schedule::id(), + create_account_shared_data_for_test(bank.epoch_schedule()), + )); let interpreted = matches.value_of("mode").unwrap() != "jit"; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); // Adding `DELAY_VISIBILITY_SLOT_OFFSET` to slots to accommodate for delay visibility of the program - let slot = bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET; let mut loaded_programs = - LoadedProgramsForTxBatch::new(slot, bank.get_runtime_environments_for_slot(slot)); + bank.new_program_cache_for_tx_batch_for_slot(bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET); for key in cached_account_keys { loaded_programs.replenish(key, bank.load_program(&key, false, bank.epoch())); debug!("Loaded program {}", key); diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 5b2d41791..8259c2ed2 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -2,7 +2,9 @@ use { crate::{ compute_budget::ComputeBudget, ic_msg, - loaded_programs::{LoadedProgram, LoadedProgramType, LoadedProgramsForTxBatch}, + loaded_programs::{ + LoadedProgram, LoadedProgramType, LoadedProgramsForTxBatch, ProgramRuntimeEnvironments, + }, log_collector::LogCollector, stable_log, sysvar_cache::SysvarCache, @@ -17,8 +19,10 @@ use { vm::{Config, ContextObject, EbpfVm}, }, solana_sdk::{ - account::AccountSharedData, + account::{create_account_shared_data_for_test, AccountSharedData}, bpf_loader_deprecated, + clock::Slot, + epoch_schedule::EpochSchedule, feature_set::FeatureSet, hash::Hash, instruction::{AccountMeta, InstructionError}, @@ -26,6 +30,7 @@ use { pubkey::Pubkey, saturating_add_assign, stable_layout::stable_instruction::StableInstruction, + sysvar, transaction_context::{ IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, }, @@ -209,6 +214,17 @@ impl<'a> InvokeContext<'a> { .or_else(|| self.programs_loaded_for_tx_batch.find(pubkey)) } + pub fn get_environments_for_slot( + &self, + effective_slot: Slot, + ) -> Result<&ProgramRuntimeEnvironments, InstructionError> { + let epoch_schedule = self.sysvar_cache.get_epoch_schedule()?; + let epoch = epoch_schedule.get_epoch(effective_slot); + Ok(self + .programs_loaded_for_tx_batch + .get_environments_for_epoch(epoch)) + } + /// Push a stack frame onto the invocation stack pub fn push(&mut self) -> Result<(), InstructionError> { let instruction_context = self @@ -713,6 +729,18 @@ pub fn mock_process_instruction>, slot: Slot, pub environments: ProgramRuntimeEnvironments, + /// Anticipated replacement for `environments` at the next epoch. + /// + /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch). + /// More precisely, it starts with the recompilation phase a few hundred slots before the epoch boundary, + /// and it ends with the first rerooting after the epoch boundary. + /// Needed when a program is deployed at the last slot of an epoch, becomes effective in the next epoch. + /// So needs to be compiled with the environment for the next epoch. + pub upcoming_environments: Option, + /// The epoch of the last rerooting + pub latest_root_epoch: Epoch, pub hit_max_limit: bool, } impl LoadedProgramsForTxBatch { - pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self { + pub fn new( + slot: Slot, + environments: ProgramRuntimeEnvironments, + upcoming_environments: Option, + latest_root_epoch: Epoch, + ) -> Self { Self { entries: HashMap::new(), slot, environments, + upcoming_environments, + latest_root_epoch, hit_max_limit: false, } } + pub fn new_from_cache( + slot: Slot, + epoch: Epoch, + cache: &ProgramCache, + ) -> Self { + Self { + entries: HashMap::new(), + slot, + environments: cache.get_environments_for_epoch(epoch).clone(), + upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch), + latest_root_epoch: cache.latest_root_epoch, + hit_max_limit: false, + } + } + + /// Returns the current environments depending on the given epoch + pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments { + if epoch != self.latest_root_epoch { + if let Some(upcoming_environments) = self.upcoming_environments.as_ref() { + return upcoming_environments; + } + } + &self.environments + } + /// Refill the cache with a single entry. It's typically called during transaction loading, and /// transaction processing (for program management instructions). /// It replaces the existing entry (if any) with the provided entry. The return value contains @@ -710,6 +752,17 @@ impl ProgramCache { &self.environments } + /// Returns the upcoming environments depending on the given epoch + pub fn get_upcoming_environments_for_epoch( + &self, + epoch: Epoch, + ) -> Option { + if epoch == self.latest_root_epoch { + return self.upcoming_environments.clone(); + } + None + } + /// Insert a single entry. It's typically called during transaction loading, /// when the cache doesn't contain the entry corresponding to program `key`. pub fn assign_program(&mut self, key: Pubkey, entry: Arc) -> bool { @@ -2057,7 +2110,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 3)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 20, 22)); @@ -2073,7 +2126,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 15)); @@ -2096,7 +2149,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(18, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(18, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 18)); @@ -2114,7 +2167,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 23)); @@ -2132,7 +2185,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(11, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(11, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 11)); @@ -2170,7 +2223,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); // Since the fork was pruned, we should not find the entry deployed at slot 20. @@ -2187,7 +2240,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 27)); @@ -2219,7 +2272,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 23)); @@ -2274,7 +2327,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 12)); @@ -2294,7 +2347,7 @@ mod tests { ), (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program2, 11, 12)); @@ -2360,7 +2413,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 19)); @@ -2374,7 +2427,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 27)); @@ -2388,7 +2441,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 20, 22)); @@ -2469,7 +2522,7 @@ mod tests { cache.prune(10, 0); let mut missing = vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))]; - let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); // The cache should have the program deployed at slot 0 @@ -2513,7 +2566,7 @@ mod tests { (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); @@ -2523,7 +2576,7 @@ mod tests { (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 5, 6)); @@ -2537,7 +2590,7 @@ mod tests { (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); @@ -2547,7 +2600,7 @@ mod tests { (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 6)); @@ -2561,7 +2614,7 @@ mod tests { (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; - let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone(), None, 0); cache.extract(&mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 2cae8b502..41af14aab 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -51,7 +51,7 @@ use { rc::Rc, sync::{atomic::Ordering, Arc}, }, - syscalls::create_program_runtime_environment_v1, + syscalls::{create_program_runtime_environment_v1, morph_into_deployment_environment_v1}, }; pub const DEFAULT_LOADER_COMPUTE_UNITS: u64 = 570; @@ -106,11 +106,16 @@ macro_rules! deploy_program { $account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{ let mut load_program_metrics = LoadProgramMetrics::default(); let mut register_syscalls_time = Measure::start("register_syscalls_time"); - let deployment_program_runtime_environment = create_program_runtime_environment_v1( - &$invoke_context.feature_set, - $invoke_context.get_compute_budget(), - true, /* deployment */ - false, /* debugging_features */ + let deployment_slot: Slot = $slot; + let environments = $invoke_context.get_environments_for_slot( + deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET) + ).map_err(|e| { + // This will never fail since the epoch schedule is already configured. + ic_msg!($invoke_context, "Failed to get runtime environment: {}", e); + InstructionError::ProgramEnvironmentSetupFailure + })?; + let deployment_program_runtime_environment = morph_into_deployment_environment_v1( + environments.program_runtime_v1.clone(), ).map_err(|e| { ic_msg!($invoke_context, "Failed to register syscalls: {}", e); InstructionError::ProgramEnvironmentSetupFailure @@ -143,7 +148,7 @@ macro_rules! deploy_program { $loader_key, $account_size, $slot, - $invoke_context.programs_modified_by_tx.environments.program_runtime_v1.clone(), + environments.program_runtime_v1.clone(), true, )?; if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) { @@ -1536,6 +1541,7 @@ mod tests { }, account_utils::StateMut, clock::Clock, + epoch_schedule::EpochSchedule, instruction::{AccountMeta, InstructionError}, pubkey::Pubkey, rent::Rent, @@ -3723,7 +3729,10 @@ mod tests { #[test] fn test_program_usage_count_on_upgrade() { - let transaction_accounts = vec![]; + let transaction_accounts = vec![( + sysvar::epoch_schedule::id(), + create_account_for_test(&EpochSchedule::default()), + )]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); let env = Arc::new(BuiltinProgram::new_mock()); @@ -3763,7 +3772,10 @@ mod tests { #[test] fn test_program_usage_count_on_non_upgrade() { - let transaction_accounts = vec![]; + let transaction_accounts = vec![( + sysvar::epoch_schedule::id(), + create_account_for_test(&EpochSchedule::default()), + )]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); let env = Arc::new(BuiltinProgram::new_mock()); diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index d9c66f24e..4a166fa1c 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -239,6 +239,24 @@ macro_rules! register_feature_gated_function { }; } +pub fn morph_into_deployment_environment_v1( + from: Arc>, +) -> Result, Error> { + let mut config = *from.get_config(); + config.reject_broken_elfs = true; + + let mut result = FunctionRegistry::>::default(); + + for (key, (name, value)) in from.get_function_registry().iter() { + // Deployment of programs with sol_alloc_free is disabled. So do not register the syscall. + if name != *b"sol_alloc_free_" { + result.register_function(key, name, value)?; + } + } + + Ok(BuiltinProgram::new_loader(config, result)) +} + pub fn create_program_runtime_environment_v1<'a>( feature_set: &FeatureSet, compute_budget: &ComputeBudget, diff --git a/programs/loader-v4/src/lib.rs b/programs/loader-v4/src/lib.rs index 9573f9250..6a9026f25 100644 --- a/programs/loader-v4/src/lib.rs +++ b/programs/loader-v4/src/lib.rs @@ -405,17 +405,21 @@ pub fn process_instruction_deploy( let deployment_slot = state.slot; let effective_slot = deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET); + let environments = invoke_context + .get_environments_for_slot(effective_slot) + .map_err(|err| { + // This will never fail since the epoch schedule is already configured. + ic_logger_msg!(log_collector, "Failed to get runtime environment {}", err); + InstructionError::InvalidArgument + })?; + let mut load_program_metrics = LoadProgramMetrics { program_id: buffer.get_key().to_string(), ..LoadProgramMetrics::default() }; let executor = LoadedProgram::new( &loader_v4::id(), - invoke_context - .programs_modified_by_tx - .environments - .program_runtime_v2 - .clone(), + environments.program_runtime_v2.clone(), deployment_slot, effective_slot, programdata, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 702711231..7b1697ddb 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -33,10 +33,6 @@ //! It offers a high-level API that signs transactions //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. -#[cfg(feature = "dev-context-only-utils")] -use solana_accounts_db::accounts_db::{ - ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS, ACCOUNTS_DB_CONFIG_FOR_TESTING, -}; #[allow(deprecated)] use solana_sdk::recent_blockhashes_account; pub use solana_sdk::reward_type::RewardType; @@ -199,6 +195,13 @@ use { time::{Duration, Instant}, }, }; +#[cfg(feature = "dev-context-only-utils")] +use { + solana_accounts_db::accounts_db::{ + ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS, ACCOUNTS_DB_CONFIG_FOR_TESTING, + }, + solana_program_runtime::loaded_programs::LoadedProgramsForTxBatch, +}; /// params to `verify_accounts_hash` struct VerifyAccountsHashConfig { @@ -7881,6 +7884,14 @@ impl Bank { pub fn update_accounts_hash_for_tests(&self) -> AccountsHash { self.update_accounts_hash(CalcAccountsHashDataSource::IndexForTests, false, false) } + + pub fn new_program_cache_for_tx_batch_for_slot(&self, slot: Slot) -> LoadedProgramsForTxBatch { + LoadedProgramsForTxBatch::new_from_cache( + slot, + self.epoch_schedule.get_epoch(slot), + &self.program_cache.read().unwrap(), + ) + } } /// Compute how much an account has changed size. This function is useful when the data size delta diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index ea2354ef8..edeeb6219 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -13921,3 +13921,115 @@ fn test_check_execution_status_and_charge_fee() { } }); } +#[test] +fn test_deploy_last_epoch_slot() { + solana_logger::setup(); + + // Bank Setup + let (mut genesis_config, mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + genesis_config + .accounts + .remove(&feature_set::reject_callx_r10::id()); + let mut bank = Bank::new_for_tests(&genesis_config); + bank.activate_feature(&feature_set::reject_callx_r10::id()); + + // go to the last slot in the epoch + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let slots_in_epoch = bank.epoch_schedule().get_slots_in_epoch(0); + let bank = new_bank_from_parent_with_bank_forks( + &bank_forks, + bank, + &Pubkey::default(), + slots_in_epoch - 1, + ); + eprintln!("now at slot {} epoch {}", bank.slot(), bank.epoch()); + + // deploy a program + let payer_keypair = Keypair::new(); + let program_keypair = Keypair::new(); + let mut file = File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so").unwrap(); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let min_program_balance = + bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); + let min_buffer_balance = bank + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_buffer(elf.len())); + let min_programdata_balance = bank.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::size_of_programdata(elf.len()), + ); + let buffer_address = Pubkey::new_unique(); + let (programdata_address, _) = Pubkey::find_program_address( + &[program_keypair.pubkey().as_ref()], + &bpf_loader_upgradeable::id(), + ); + let upgrade_authority_keypair = Keypair::new(); + + let buffer_account = { + let mut account = AccountSharedData::new( + min_buffer_balance, + UpgradeableLoaderState::size_of_buffer(elf.len()), + &bpf_loader_upgradeable::id(), + ); + account + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(upgrade_authority_keypair.pubkey()), + }) + .unwrap(); + account + .data_as_mut_slice() + .get_mut(UpgradeableLoaderState::size_of_buffer_metadata()..) + .unwrap() + .copy_from_slice(&elf); + account + }; + + let payer_base_balance = LAMPORTS_PER_SOL; + let deploy_fees = { + let fee_calculator = genesis_config.fee_rate_governor.create_fee_calculator(); + 3 * fee_calculator.lamports_per_signature + }; + let min_payer_balance = min_program_balance + .saturating_add(min_programdata_balance) + .saturating_sub(min_buffer_balance.saturating_add(deploy_fees)); + bank.store_account( + &payer_keypair.pubkey(), + &AccountSharedData::new( + payer_base_balance.saturating_add(min_payer_balance), + 0, + &system_program::id(), + ), + ); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &AccountSharedData::default()); + bank.store_account(&programdata_address, &AccountSharedData::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &payer_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + &upgrade_authority_keypair.pubkey(), + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&payer_keypair.pubkey()), + ); + let signers = &[&payer_keypair, &program_keypair, &upgrade_authority_keypair]; + let transaction = Transaction::new(signers, message.clone(), bank.last_blockhash()); + let ret = bank.process_transaction(&transaction); + assert!(ret.is_ok(), "ret: {:?}", ret); + goto_end_of_slot(bank.clone()); + + // go to the first slot in the new epoch + let bank = + new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slots_in_epoch); + eprintln!("now at slot {} epoch {}", bank.slot(), bank.epoch()); + + let instruction = Instruction::new_with_bytes(program_keypair.pubkey(), &[], Vec::new()); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let binding = mint_keypair.insecure_clone(); + let signers = vec![&binding]; + let transaction = Transaction::new(&signers, message, bank.last_blockhash()); + let result_with_feature_enabled = bank.process_transaction(&transaction); + assert_eq!(result_with_feature_enabled, Ok(())); +} diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index db5a8ac92..b1673cef0 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -510,9 +510,10 @@ impl TransactionBatchProcessor { // Initialize our local cache. let is_first_round = loaded_programs_for_txs.is_none(); if is_first_round { - loaded_programs_for_txs = Some(LoadedProgramsForTxBatch::new( + loaded_programs_for_txs = Some(LoadedProgramsForTxBatch::new_from_cache( self.slot, - program_cache.get_environments_for_epoch(self.epoch).clone(), + self.epoch, + &program_cache, )); } // Submit our last completed loading task. @@ -523,9 +524,10 @@ impl TransactionBatchProcessor { // This branch is taken when there is an error in assigning a program to a // cache slot. It is not possible to mock this error for SVM unit // tests purposes. - let mut ret = LoadedProgramsForTxBatch::new( + let mut ret = LoadedProgramsForTxBatch::new_from_cache( self.slot, - program_cache.get_environments_for_epoch(self.epoch).clone(), + self.epoch, + &program_cache, ); ret.hit_max_limit = true; return ret; @@ -630,6 +632,8 @@ impl TransactionBatchProcessor { let mut programs_modified_by_tx = LoadedProgramsForTxBatch::new( self.slot, programs_loaded_for_tx_batch.environments.clone(), + programs_loaded_for_tx_batch.upcoming_environments.clone(), + programs_loaded_for_tx_batch.latest_root_epoch, ); let mut process_message_time = Measure::start("process_message_time"); let process_result = MessageProcessor::process_message(