Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit ddaf56d

Browse files
mergify[bot]Lichtso
authored andcommitted
v1.18: Fix - Reloading of unload entries after finishing the recompilation phase (backport of #1199) (#1275)
* Fix - Reloading of unload entries after finishing the recompilation phase (#1199) * Differentiate entries by environment instead of adjusting the effective slot. * Adds comments inside ProgramCache::assign_program(). * Adds reproducer to test_feature_activation_loaded_programs_epoch_transition. * Fixes env order in test_feature_activation_loaded_programs_recompilation_phase(). * Syncs both tests with master. --------- (cherry picked from commit 979f619) Co-authored-by: Alexander Meißner <[email protected]>
1 parent dfdb234 commit ddaf56d

File tree

3 files changed

+96
-42
lines changed

3 files changed

+96
-42
lines changed

program-runtime/src/loaded_programs.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,11 +719,33 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
719719
/// Insert a single entry. It's typically called during transaction loading,
720720
/// when the cache doesn't contain the entry corresponding to program `key`.
721721
pub fn assign_program(&mut self, key: Pubkey, entry: Arc<LoadedProgram>) -> bool {
722+
// This function always returns `true` during normal operation.
723+
// Only during the recompilation phase this can return `false`
724+
// for entries with `upcoming_environments`.
725+
fn is_current_env(
726+
environments: &ProgramRuntimeEnvironments,
727+
env_opt: Option<&ProgramRuntimeEnvironment>,
728+
) -> bool {
729+
env_opt
730+
.map(|env| {
731+
Arc::ptr_eq(env, &environments.program_runtime_v1)
732+
|| Arc::ptr_eq(env, &environments.program_runtime_v2)
733+
})
734+
.unwrap_or(true)
735+
}
722736
let slot_versions = &mut self.entries.entry(key).or_default().slot_versions;
723737
match slot_versions.binary_search_by(|at| {
724738
at.effective_slot
725739
.cmp(&entry.effective_slot)
726740
.then(at.deployment_slot.cmp(&entry.deployment_slot))
741+
.then(
742+
// This `.then()` has no effect during normal operation.
743+
// Only during the recompilation phase this does allow entries
744+
// which only differ in their environment to be interleaved in `slot_versions`.
745+
is_current_env(&self.environments, at.program.get_environment()).cmp(
746+
&is_current_env(&self.environments, entry.program.get_environment()),
747+
),
748+
)
727749
}) {
728750
Ok(index) => {
729751
let existing = slot_versions.get_mut(index).unwrap();

runtime/src/bank.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4728,21 +4728,6 @@ impl Bank {
47284728

47294729
let mut timings = ExecuteDetailsTimings::default();
47304730
load_program_metrics.submit_datapoint(&mut timings);
4731-
if !Arc::ptr_eq(
4732-
&environments.program_runtime_v1,
4733-
&loaded_programs_cache.environments.program_runtime_v1,
4734-
) || !Arc::ptr_eq(
4735-
&environments.program_runtime_v2,
4736-
&loaded_programs_cache.environments.program_runtime_v2,
4737-
) {
4738-
// There can be two entries per program when the environment changes.
4739-
// One for the old environment before the epoch boundary and one for the new environment after the epoch boundary.
4740-
// These two entries have the same deployment slot, so they must differ in their effective slot instead.
4741-
// This is done by setting the effective slot of the entry for the new environment to the epoch boundary.
4742-
loaded_program.effective_slot = loaded_program
4743-
.effective_slot
4744-
.max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch));
4745-
}
47464731
if let Some(recompile) = recompile {
47474732
loaded_program.tx_usage_counter =
47484733
AtomicU64::new(recompile.tx_usage_counter.load(Ordering::Relaxed));

runtime/src/bank/tests.rs

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12058,12 +12058,6 @@ fn test_feature_activation_loaded_programs_recompilation_phase() {
1205812058
.remove(&feature_set::reject_callx_r10::id());
1205912059
let (root_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1206012060

12061-
// Test a basic transfer
12062-
let amount = genesis_config.rent.minimum_balance(0);
12063-
let pubkey = solana_sdk::pubkey::new_rand();
12064-
root_bank.transfer(amount, &mint_keypair, &pubkey).unwrap();
12065-
assert_eq!(root_bank.get_balance(&pubkey), amount);
12066-
1206712061
// Program Setup
1206812062
let program_keypair = Keypair::new();
1206912063
let program_data =
@@ -12077,26 +12071,19 @@ fn test_feature_activation_loaded_programs_recompilation_phase() {
1207712071
});
1207812072
root_bank.store_account(&program_keypair.pubkey(), &program_account);
1207912073

12080-
// Compose instruction using the desired program
12081-
let instruction1 = Instruction::new_with_bytes(program_keypair.pubkey(), &[], Vec::new());
12082-
let message1 = Message::new(&[instruction1], Some(&mint_keypair.pubkey()));
12083-
let binding1 = mint_keypair.insecure_clone();
12084-
let signers1 = vec![&binding1];
12085-
let transaction1 = Transaction::new(&signers1, message1, root_bank.last_blockhash());
12074+
// Compose message using the desired program.
12075+
let instruction = Instruction::new_with_bytes(program_keypair.pubkey(), &[], Vec::new());
12076+
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
12077+
let binding = mint_keypair.insecure_clone();
12078+
let signers = vec![&binding];
1208612079

12087-
// Advance the bank so the next transaction can be submitted.
12080+
// Advance the bank so that the program becomes effective.
1208812081
goto_end_of_slot(root_bank.clone());
1208912082
let bank = new_from_parent_with_fork_next_slot(root_bank, bank_forks.as_ref());
1209012083

12091-
// Compose second instruction using the same program with a different block hash
12092-
let instruction2 = Instruction::new_with_bytes(program_keypair.pubkey(), &[], Vec::new());
12093-
let message2 = Message::new(&[instruction2], Some(&mint_keypair.pubkey()));
12094-
let binding2 = mint_keypair.insecure_clone();
12095-
let signers2 = vec![&binding2];
12096-
let transaction2 = Transaction::new(&signers2, message2, bank.last_blockhash());
12097-
12098-
// Execute before feature is enabled to get program into the cache.
12099-
let result_without_feature_enabled = bank.process_transaction(&transaction1);
12084+
// Load the program with the old environment.
12085+
let transaction = Transaction::new(&signers, message.clone(), bank.last_blockhash());
12086+
let result_without_feature_enabled = bank.process_transaction(&transaction);
1210012087
assert_eq!(
1210112088
result_without_feature_enabled,
1210212089
Err(TransactionError::InstructionError(
@@ -12105,20 +12092,65 @@ fn test_feature_activation_loaded_programs_recompilation_phase() {
1210512092
))
1210612093
);
1210712094

12108-
// Activate feature
12095+
// Schedule feature activation to trigger a change of environment at the epoch boundary.
1210912096
let feature_account_balance =
1211012097
std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1);
1211112098
bank.store_account(
1211212099
&feature_set::reject_callx_r10::id(),
1211312100
&feature::create_account(&Feature { activated_at: None }, feature_account_balance),
1211412101
);
1211512102

12103+
// Advance the bank to middle of epoch to start the recompilation phase.
12104+
goto_end_of_slot(bank.clone());
12105+
let bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 16);
12106+
let current_env = bank
12107+
.loaded_programs_cache
12108+
.read()
12109+
.unwrap()
12110+
.get_environments_for_epoch(0)
12111+
.program_runtime_v1
12112+
.clone();
12113+
let upcoming_env = bank
12114+
.loaded_programs_cache
12115+
.read()
12116+
.unwrap()
12117+
.get_environments_for_epoch(1)
12118+
.program_runtime_v1
12119+
.clone();
12120+
12121+
// Advance the bank to recompile the program.
12122+
{
12123+
let program_cache = bank.loaded_programs_cache.read().unwrap();
12124+
let slot_versions = program_cache.get_slot_versions_for_tests(&program_keypair.pubkey());
12125+
assert_eq!(slot_versions.len(), 1);
12126+
assert!(Arc::ptr_eq(
12127+
slot_versions[0].program.get_environment().unwrap(),
12128+
&current_env
12129+
));
12130+
}
12131+
goto_end_of_slot(bank.clone());
12132+
let bank = new_from_parent_with_fork_next_slot(bank, bank_forks.as_ref());
12133+
{
12134+
let program_cache = bank.loaded_programs_cache.write().unwrap();
12135+
let slot_versions = program_cache.get_slot_versions_for_tests(&program_keypair.pubkey());
12136+
assert_eq!(slot_versions.len(), 2);
12137+
assert!(Arc::ptr_eq(
12138+
slot_versions[0].program.get_environment().unwrap(),
12139+
&upcoming_env
12140+
));
12141+
assert!(Arc::ptr_eq(
12142+
slot_versions[1].program.get_environment().unwrap(),
12143+
&current_env
12144+
));
12145+
}
12146+
12147+
// Advance the bank to cross the epoch boundary and activate the feature.
1211612148
goto_end_of_slot(bank.clone());
12117-
// Advance to next epoch, which starts the recompilation phase
12118-
let bank = new_from_parent_next_epoch(bank, bank_forks.as_ref(), 1);
12149+
let bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33);
1211912150

12120-
// Execute after feature is enabled to check it was filtered out and reverified.
12121-
let result_with_feature_enabled = bank.process_transaction(&transaction2);
12151+
// Load the program with the new environment.
12152+
let transaction = Transaction::new(&signers, message, bank.last_blockhash());
12153+
let result_with_feature_enabled = bank.process_transaction(&transaction);
1212212154
assert_eq!(
1212312155
result_with_feature_enabled,
1212412156
Err(TransactionError::InstructionError(
@@ -12178,6 +12210,21 @@ fn test_feature_activation_loaded_programs_epoch_transition() {
1217812210
let bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33);
1217912211

1218012212
// Load the program with the new environment.
12213+
let transaction = Transaction::new(&signers, message.clone(), bank.last_blockhash());
12214+
assert!(bank.process_transaction(&transaction).is_ok());
12215+
12216+
{
12217+
// Prune for rerooting and thus finishing the recompilation phase.
12218+
let mut program_cache = bank.loaded_programs_cache.write().unwrap();
12219+
program_cache.prune(bank.slot(), bank.epoch());
12220+
12221+
// Unload all (which is only the entry with the new environment)
12222+
program_cache.sort_and_unload(percentage::Percentage::from(0));
12223+
}
12224+
12225+
// Reload the unloaded program with the new environment.
12226+
goto_end_of_slot(bank.clone());
12227+
let bank = new_from_parent_with_fork_next_slot(bank, bank_forks.as_ref());
1218112228
let transaction = Transaction::new(&signers, message, bank.last_blockhash());
1218212229
assert!(bank.process_transaction(&transaction).is_ok());
1218312230
}

0 commit comments

Comments
 (0)