Fix: deploy program on last slot of epoch during environment change (#101)

* Fix: deploy program on last slot of epoch during environment change

* solana-runtime: deploy at last epoch slot test

* disable deployment of sol_alloc_free

* Move tx-batch-constructor to its own function

* use new_from_cache

---------

Co-authored-by: Alessandro Decina <alessandro.d@gmail.com>
This commit is contained in:
Pankaj Garg 2024-03-22 07:43:28 -07:00 committed by GitHub
parent 36c66f5111
commit 91b1ee3df6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 296 additions and 47 deletions

View File

@ -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);

View File

@ -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<F: FnMut(&mut InvokeContext), G: FnMut(&mut Invo
program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
transaction_accounts.push((*loader_id, processor_account));
let pop_epoch_schedule_account = if !transaction_accounts
.iter()
.any(|(key, _)| *key == sysvar::epoch_schedule::id())
{
transaction_accounts.push((
sysvar::epoch_schedule::id(),
create_account_shared_data_for_test(&EpochSchedule::default()),
));
true
} else {
false
};
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let mut programs_loaded_for_tx_batch = LoadedProgramsForTxBatch::default();
programs_loaded_for_tx_batch.replenish(
@ -731,6 +759,9 @@ pub fn mock_process_instruction<F: FnMut(&mut InvokeContext), G: FnMut(&mut Invo
assert_eq!(result, expected_result);
post_adjustments(&mut invoke_context);
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
if pop_epoch_schedule_account {
transaction_accounts.pop();
}
transaction_accounts.pop();
transaction_accounts
}

View File

@ -618,19 +618,61 @@ pub struct LoadedProgramsForTxBatch {
entries: HashMap<Pubkey, Arc<LoadedProgram>>,
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<ProgramRuntimeEnvironments>,
/// 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<ProgramRuntimeEnvironments>,
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<FG: ForkGraph>(
slot: Slot,
epoch: Epoch,
cache: &ProgramCache<FG>,
) -> 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<FG: ForkGraph> ProgramCache<FG> {
&self.environments
}
/// Returns the upcoming environments depending on the given epoch
pub fn get_upcoming_environments_for_epoch(
&self,
epoch: Epoch,
) -> Option<ProgramRuntimeEnvironments> {
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<LoadedProgram>) -> 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));

View File

@ -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());

View File

@ -239,6 +239,24 @@ macro_rules! register_feature_gated_function {
};
}
pub fn morph_into_deployment_environment_v1(
from: Arc<BuiltinProgram<InvokeContext>>,
) -> Result<BuiltinProgram<InvokeContext>, Error> {
let mut config = *from.get_config();
config.reject_broken_elfs = true;
let mut result = FunctionRegistry::<BuiltinFunction<InvokeContext>>::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,

View File

@ -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,

View File

@ -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

View File

@ -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(()));
}

View File

@ -510,9 +510,10 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
// 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<FG: ForkGraph> TransactionBatchProcessor<FG> {
// 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<FG: ForkGraph> TransactionBatchProcessor<FG> {
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(