From 85f6c36603838a58454417da07f4a113dbcea70a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:38:21 -0700 Subject: [PATCH] v1.18: Fix: deploy program on last slot of epoch during environment change (backport of #101) (#387) --- ledger-tool/src/program.rs | 17 ++-- program-runtime/src/invoke_context.rs | 35 +++++++- program-runtime/src/loaded_programs.rs | 101 ++++++++++++++++----- programs/bpf_loader/src/lib.rs | 30 +++++-- programs/bpf_loader/src/syscalls/mod.rs | 18 ++++ programs/loader-v4/src/lib.rs | 14 +-- runtime/src/bank.rs | 23 +++-- runtime/src/bank/tests.rs | 113 ++++++++++++++++++++++++ 8 files changed, 297 insertions(+), 54 deletions(-) diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index fba9076de2c5bd..7024cd5baada50 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,18 +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 mut loaded_programs = LoadedProgramsForTxBatch::new( - bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET, - bank.loaded_programs_cache - .read() - .unwrap() - .environments - .clone(), - ); + let mut loaded_programs = + 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, None)); debug!("Loaded program {}", key); diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 5b2d417912256f..8259c2ed2bcc7a 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: &LoadedPrograms, + ) -> 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 @@ -660,6 +702,17 @@ impl LoadedPrograms { &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 { @@ -2063,7 +2116,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)); @@ -2079,7 +2132,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)); @@ -2102,7 +2155,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)); @@ -2120,7 +2173,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)); @@ -2138,7 +2191,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)); @@ -2172,7 +2225,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program4, (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)); @@ -2190,7 +2243,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); assert!(match_slot(&extracted, &program1, 0, 21)); @@ -2228,7 +2281,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. @@ -2245,7 +2298,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)); @@ -2277,7 +2330,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)); @@ -2332,7 +2385,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)); @@ -2352,7 +2405,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)); @@ -2418,7 +2471,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)); @@ -2432,7 +2485,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)); @@ -2446,7 +2499,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)); @@ -2511,7 +2564,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); // Program1 deployed at slot 11 should not be expired yet @@ -2527,7 +2580,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program3, (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, &program2, 11, 15)); @@ -2593,7 +2646,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 @@ -2637,7 +2690,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)); @@ -2647,7 +2700,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)); @@ -2661,7 +2714,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)); @@ -2671,7 +2724,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)); @@ -2685,7 +2738,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 43bff889b0aff8..665d90ac771e49 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -52,7 +52,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; @@ -109,11 +109,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 @@ -146,7 +151,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) { @@ -1545,6 +1550,7 @@ mod tests { }, account_utils::StateMut, clock::Clock, + epoch_schedule::EpochSchedule, instruction::{AccountMeta, InstructionError}, pubkey::Pubkey, rent::Rent, @@ -3732,7 +3738,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()); @@ -3773,7 +3782,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 d9c66f24e503ed..4a166fa1cf9996 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 6afc03067f7bcc..8b99addd45437c 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, None, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index cf4f56edabe8cc..557d24b8f0dc67 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -4828,6 +4828,8 @@ impl Bank { 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( @@ -4977,11 +4979,10 @@ impl Bank { // 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, - loaded_programs_cache - .get_environments_for_epoch(self.epoch) - .clone(), + self.epoch, + &loaded_programs_cache, )); } // Submit our last completed loading task. @@ -4990,7 +4991,11 @@ impl Bank { .finish_cooperative_loading_task(self.slot, key, program) && limit_to_load_programs { - let mut ret = LoadedProgramsForTxBatch::default(); + let mut ret = LoadedProgramsForTxBatch::new_from_cache( + self.slot, + self.epoch, + &loaded_programs_cache, + ); ret.hit_max_limit = true; return ret; } @@ -8333,6 +8338,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.loaded_programs_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 4a57e21415b615..39bf2e5169c435 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -14123,3 +14123,116 @@ fn test_failed_simulation_compute_units() { let simulation = bank.simulate_transaction(&sanitized, false); assert_eq!(expected_consumed_units, simulation.units_consumed); } + +#[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(())); +}