From 2d370f6888c1ded842e2a41ee90ff080962bc656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Wed, 21 Feb 2024 23:26:54 +0000 Subject: [PATCH 1/4] Always limit effective slot to the begin of the current epoch. --- svm/src/transaction_processor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index b58d178df4b963..d877bf3d09b54c 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -739,10 +739,10 @@ impl TransactionBatchProcessor { let mut timings = ExecuteDetailsTimings::default(); load_program_metrics.submit_datapoint(&mut timings); + loaded_program.effective_slot = loaded_program + .effective_slot + .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); if let Some(recompile) = recompile { - loaded_program.effective_slot = loaded_program - .effective_slot - .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); loaded_program.tx_usage_counter = AtomicU64::new(recompile.tx_usage_counter.load(Ordering::Relaxed)); loaded_program.ix_usage_counter = From 2fa7f8744b5ab66f6f9a9d72a5fe6f6986fe397b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Thu, 22 Feb 2024 12:47:46 +0000 Subject: [PATCH 2/4] Adds comments. --- svm/src/transaction_processor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index d877bf3d09b54c..a44f1f7e177416 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -739,6 +739,10 @@ impl TransactionBatchProcessor { let mut timings = ExecuteDetailsTimings::default(); load_program_metrics.submit_datapoint(&mut timings); + // There can be two entries per program when the environment changes. + // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary. + // These two entries have the same deployment slot, so they must differ in their effective slot instead. + // This is done by setting the effective slot of the entry for the new environment to the epoch boundary. loaded_program.effective_slot = loaded_program .effective_slot .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); From 41cc4f884ad8547ee4db2939b2b53fc443970b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 23 Feb 2024 09:46:29 +0000 Subject: [PATCH 3/4] Optimizes to avoid having two entries if there is no relevant feature activation. --- svm/src/transaction_processor.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index a44f1f7e177416..dc3e59389cc295 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -739,13 +739,21 @@ impl TransactionBatchProcessor { let mut timings = ExecuteDetailsTimings::default(); load_program_metrics.submit_datapoint(&mut timings); - // There can be two entries per program when the environment changes. - // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary. - // These two entries have the same deployment slot, so they must differ in their effective slot instead. - // This is done by setting the effective slot of the entry for the new environment to the epoch boundary. - loaded_program.effective_slot = loaded_program - .effective_slot - .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); + if !Arc::ptr_eq( + &environments.program_runtime_v1, + &loaded_programs_cache.environments.program_runtime_v1, + ) || !Arc::ptr_eq( + &environments.program_runtime_v2, + &loaded_programs_cache.environments.program_runtime_v2, + ) { + // There can be two entries per program when the environment changes. + // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary. + // These two entries have the same deployment slot, so they must differ in their effective slot instead. + // This is done by setting the effective slot of the entry for the new environment to the epoch boundary. + loaded_program.effective_slot = loaded_program + .effective_slot + .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); + } if let Some(recompile) = recompile { loaded_program.tx_usage_counter = AtomicU64::new(recompile.tx_usage_counter.load(Ordering::Relaxed)); From 78f7d6ac94e959a6b9cabd83dc01883e5dec4187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 23 Feb 2024 10:21:43 +0000 Subject: [PATCH 4/4] Adds test_feature_activation_loaded_programs_epoch_transition(). --- runtime/src/bank/tests.rs | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index a01e9c19de6a39..24ef89c8c9db26 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -12018,6 +12018,60 @@ fn test_feature_activation_loaded_programs_recompilation_phase() { ); } +#[test] +fn test_feature_activation_loaded_programs_epoch_transition() { + 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 (root_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); + + // Program Setup + let program_keypair = Keypair::new(); + let program_data = include_bytes!("../../../programs/bpf_loader/test_elfs/out/noop_aligned.so"); + let program_account = AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(program_data.len()).min(1), + data: program_data.to_vec(), + owner: bpf_loader::id(), + executable: true, + rent_epoch: 0, + }); + root_bank.store_account(&program_keypair.pubkey(), &program_account); + + // Compose message using the desired program. + 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]; + + // Advance the bank so that the program becomes effective. + goto_end_of_slot(root_bank.clone()); + let bank = new_from_parent_with_fork_next_slot(root_bank, bank_forks.as_ref()); + + // Load the program with the old environment. + let transaction = Transaction::new(&signers, message.clone(), bank.last_blockhash()); + assert!(bank.process_transaction(&transaction).is_ok()); + + // Schedule feature activation to trigger a change of environment at the epoch boundary. + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::reject_callx_r10::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + + // Advance the bank to cross the epoch boundary and activate the feature. + goto_end_of_slot(bank.clone()); + let bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33); + + // Load the program with the new environment. + let transaction = Transaction::new(&signers, message, bank.last_blockhash()); + assert!(bank.process_transaction(&transaction).is_ok()); +} + #[test] fn test_bank_verify_accounts_hash_with_base() { let GenesisConfigInfo {