Stop loading program accounts if program exists in cache (#30703)

* Stop loading program accounts if program exists in cache

* load accounts for upgradeable programs

* revert loader change to conditionally use program data account

* load instruction accounts

* generate TransactionExecutorCache from loaded programs

* cleanup account_found_and_dep_index variable

* address review comments

* handle tombstones in loader

* unify tombstone constructor

* handle multiple tombstones
This commit is contained in:
Pankaj Garg 2023-03-28 11:49:56 -07:00 committed by GitHub
parent e7887cfb06
commit aebc191c38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 216 additions and 135 deletions

View File

@ -1,5 +1,5 @@
use {
crate::loaded_programs::LoadedProgram,
crate::loaded_programs::{LoadedProgram, LoadedProgramType},
log::*,
rand::Rng,
solana_sdk::{pubkey::Pubkey, saturating_add_assign, slot_history::Slot, stake_history::Epoch},
@ -42,8 +42,13 @@ impl TransactionExecutorCache {
}
pub fn set_tombstone(&mut self, key: Pubkey, slot: Slot) {
self.visible
.insert(key, Arc::new(LoadedProgram::new_tombstone(slot)));
self.visible.insert(
key,
Arc::new(LoadedProgram::new_tombstone(
slot,
LoadedProgramType::Closed,
)),
);
}
pub fn set(
@ -58,7 +63,13 @@ impl TransactionExecutorCache {
if delay_visibility_of_program_deployment {
// Place a tombstone in the cache so that
// we don't load the new version from the database as it should remain invisible
self.set_tombstone(key, current_slot);
self.visible.insert(
key,
Arc::new(LoadedProgram::new_tombstone(
current_slot,
LoadedProgramType::DelayVisibility,
)),
);
} else {
self.visible.insert(key, executor.clone());
}

View File

@ -14,7 +14,6 @@ use {
solana_rbpf::vm::ContextObject,
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{
enable_early_verification_of_account_modifications, native_programs_consume_cu,
FeatureSet,
@ -624,37 +623,11 @@ impl<'a> InvokeContext<'a> {
ic_msg!(self, "Account {} is not executable", callee_program_id);
return Err(InstructionError::AccountNotExecutable);
}
let mut program_indices = vec![];
if borrowed_program_account.get_owner() == &bpf_loader_upgradeable::id() {
if let UpgradeableLoaderState::Program {
programdata_address,
} = borrowed_program_account.get_state()?
{
if let Some(programdata_account_index) = self
.transaction_context
.find_index_of_program_account(&programdata_address)
{
program_indices.push(programdata_account_index);
} else {
ic_msg!(
self,
"Unknown upgradeable programdata account {}",
programdata_address,
);
return Err(InstructionError::MissingAccount);
}
} else {
ic_msg!(
self,
"Invalid upgradeable program account {}",
callee_program_id,
);
return Err(InstructionError::MissingAccount);
}
}
program_indices.push(borrowed_program_account.get_index_in_transaction());
Ok((instruction_accounts, program_indices))
Ok((
instruction_accounts,
vec![borrowed_program_account.get_index_in_transaction()],
))
}
/// Processes an instruction and returns how many compute units were used

View File

@ -58,7 +58,9 @@ pub trait WorkingSlot {
pub enum LoadedProgramType {
/// Tombstone for undeployed, closed or unloadable programs
#[default]
Invalid,
FailedVerification,
Closed,
DelayVisibility,
LegacyV0(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
LegacyV1(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
Typed(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
@ -68,7 +70,11 @@ pub enum LoadedProgramType {
impl Debug for LoadedProgramType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LoadedProgramType::Invalid => write!(f, "LoadedProgramType::Invalid"),
LoadedProgramType::FailedVerification => {
write!(f, "LoadedProgramType::FailedVerification")
}
LoadedProgramType::Closed => write!(f, "LoadedProgramType::Closed"),
LoadedProgramType::DelayVisibility => write!(f, "LoadedProgramType::DelayVisibility"),
LoadedProgramType::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"),
LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"),
LoadedProgramType::Typed(_) => write!(f, "LoadedProgramType::Typed"),
@ -190,18 +196,25 @@ impl LoadedProgram {
}
}
pub fn new_tombstone(slot: Slot) -> Self {
Self {
program: LoadedProgramType::Invalid,
pub fn new_tombstone(slot: Slot, reason: LoadedProgramType) -> Self {
let tombstone = Self {
program: reason,
account_size: 0,
deployment_slot: slot,
effective_slot: slot,
usage_counter: AtomicU64::default(),
}
};
debug_assert!(tombstone.is_tombstone());
tombstone
}
pub fn is_tombstone(&self) -> bool {
matches!(self.program, LoadedProgramType::Invalid)
matches!(
self.program,
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility
)
}
}
@ -266,7 +279,15 @@ impl LoadedPrograms {
let existing = second_level
.get(index)
.expect("Missing entry, even though position was found");
assert!(
if existing.is_tombstone()
&& entry.is_tombstone()
&& existing.deployment_slot == entry.deployment_slot
{
// If there's already a tombstone for the program at the given slot, let's return
// the existing entry instead of adding another.
return existing.clone();
}
debug_assert!(
existing.deployment_slot != entry.deployment_slot
|| existing.effective_slot != entry.effective_slot
);
@ -395,7 +416,13 @@ mod tests {
}
fn set_tombstone(cache: &mut LoadedPrograms, key: Pubkey, slot: Slot) -> Arc<LoadedProgram> {
cache.assign_program(key, Arc::new(LoadedProgram::new_tombstone(slot)))
cache.assign_program(
key,
Arc::new(LoadedProgram::new_tombstone(
slot,
LoadedProgramType::FailedVerification,
)),
)
}
#[test]
@ -637,14 +664,17 @@ mod tests {
#[test]
fn test_tombstone() {
let tombstone = LoadedProgram::new_tombstone(0);
assert!(matches!(tombstone.program, LoadedProgramType::Invalid));
let tombstone = LoadedProgram::new_tombstone(0, LoadedProgramType::FailedVerification);
assert!(matches!(
tombstone.program,
LoadedProgramType::FailedVerification
));
assert!(tombstone.is_tombstone());
assert_eq!(tombstone.deployment_slot, 0);
assert_eq!(tombstone.effective_slot, 0);
let tombstone = LoadedProgram::new_tombstone(100);
assert!(matches!(tombstone.program, LoadedProgramType::Invalid));
let tombstone = LoadedProgram::new_tombstone(100, LoadedProgramType::Closed);
assert!(matches!(tombstone.program, LoadedProgramType::Closed));
assert!(tombstone.is_tombstone());
assert_eq!(tombstone.deployment_slot, 100);
assert_eq!(tombstone.effective_slot, 100);
@ -835,7 +865,7 @@ mod tests {
usage_counter: AtomicU64,
) -> Arc<LoadedProgram> {
Arc::new(LoadedProgram {
program: LoadedProgramType::Invalid,
program: LoadedProgramType::FailedVerification,
account_size: 0,
deployment_slot,
effective_slot,

View File

@ -135,7 +135,7 @@ pub fn load_program_from_bytes(
Ok(loaded_program)
}
fn get_programdata_offset_and_depoyment_offset(
fn get_programdata_offset_and_deployment_offset(
log_collector: &Option<Rc<RefCell<LogCollector>>>,
program: &BorrowedAccount,
programdata: &BorrowedAccount,
@ -181,14 +181,10 @@ pub fn load_program_from_account(
return Err(InstructionError::IncorrectProgramId);
}
let (programdata_offset, deployment_slot) =
get_programdata_offset_and_depoyment_offset(&log_collector, program, programdata)?;
if let Some(ref tx_executor_cache) = tx_executor_cache {
if let Some(loaded_program) = tx_executor_cache.get(program.get_key()) {
if loaded_program.is_tombstone() {
// We cached that the Executor does not exist, abort
// This case can only happen once delay_visibility_of_program_deployment is active.
return Err(InstructionError::InvalidAccountData);
}
// Executor exists and is cached, use it
@ -196,6 +192,9 @@ pub fn load_program_from_account(
}
}
let (programdata_offset, deployment_slot) =
get_programdata_offset_and_deployment_offset(&log_collector, program, programdata)?;
let programdata_size = if programdata_offset != 0 {
programdata.get_data().len()
} else {
@ -556,14 +555,16 @@ fn process_instruction_common(
ic_logger_msg!(log_collector, "Program is not executable");
return Err(InstructionError::IncorrectProgramId);
}
let programdata_account = if bpf_loader_upgradeable::check_id(program_account.get_owner()) {
let programdata_account = instruction_context.try_borrow_program_account(
transaction_context,
instruction_context
.get_number_of_program_accounts()
.saturating_sub(2),
)?;
Some(programdata_account)
instruction_context
.try_borrow_program_account(
transaction_context,
instruction_context
.get_number_of_program_accounts()
.saturating_sub(2),
)
.ok()
} else {
None
};
@ -591,7 +592,9 @@ fn process_instruction_common(
executor.usage_counter.fetch_add(1, Ordering::Relaxed);
match &executor.program {
LoadedProgramType::Invalid => Err(InstructionError::InvalidAccountData),
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => Err(InstructionError::InvalidAccountData),
LoadedProgramType::LegacyV0(executable) => execute(executable, invoke_context),
LoadedProgramType::LegacyV1(executable) => execute(executable, invoke_context),
_ => Err(InstructionError::IncorrectProgramId),

View File

@ -590,7 +590,9 @@ pub fn process_instruction(invoke_context: &mut InvokeContext) -> Result<(), Ins
drop(program);
loaded_program.usage_counter.fetch_add(1, Ordering::Relaxed);
match &loaded_program.program {
LoadedProgramType::Invalid => Err(InstructionError::InvalidAccountData),
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => Err(InstructionError::InvalidAccountData),
LoadedProgramType::Typed(executable) => execute(invoke_context, executable),
_ => Err(InstructionError::IncorrectProgramId),
}

View File

@ -24,16 +24,21 @@ use {
transaction_error_metrics::TransactionErrorMetrics,
},
dashmap::DashMap,
itertools::Itertools,
log::*,
solana_address_lookup_table_program::{error::AddressLookupError, state::AddressLookupTable},
solana_program_runtime::compute_budget::{self, ComputeBudget},
solana_program_runtime::{
compute_budget::{self, ComputeBudget},
loaded_programs::{LoadedProgram, LoadedProgramType},
},
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
bpf_loader_upgradeable,
clock::{BankId, Slot},
feature_set::{
self, add_set_tx_loaded_accounts_data_size_instruction, enable_request_heap_frame_ix,
self, add_set_tx_loaded_accounts_data_size_instruction,
delay_visibility_of_program_deployment, enable_request_heap_frame_ix,
include_loaded_accounts_data_size_in_fee_calculation,
remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix,
use_default_units_in_fee_calculation, FeatureSet,
@ -287,6 +292,38 @@ impl Accounts {
}
}
fn account_shared_data_from_program(
key: &Pubkey,
feature_set: &FeatureSet,
program: &LoadedProgram,
program_accounts: &HashMap<Pubkey, &Pubkey>,
) -> Result<AccountSharedData> {
// Check for tombstone
// Ignoring the tombstone here for now. The loader will catch this condition and return
// error.
let _ignore = match &program.program {
LoadedProgramType::FailedVerification | LoadedProgramType::Closed => {
Err(TransactionError::InvalidProgramForExecution)
}
LoadedProgramType::DelayVisibility => {
debug_assert!(feature_set.is_active(&delay_visibility_of_program_deployment::id()));
Err(TransactionError::InvalidProgramForExecution)
}
_ => Ok(()),
};
// It's an executable program account. The program is already loaded in the cache.
// So the account data is not needed. Return a dummy AccountSharedData with meta
// information.
let mut program_account = AccountSharedData::default();
let program_owner = program_accounts
.get(key)
.ok_or(TransactionError::AccountNotFound)?;
program_account.set_owner(**program_owner);
program_account.set_executable(true);
Ok(program_account)
}
#[allow(clippy::too_many_arguments)]
fn load_transaction_accounts(
&self,
ancestors: &Ancestors,
@ -296,6 +333,8 @@ impl Accounts {
rent_collector: &RentCollector,
feature_set: &FeatureSet,
account_overrides: Option<&AccountOverrides>,
program_accounts: &HashMap<Pubkey, &Pubkey>,
loaded_programs: &HashMap<Pubkey, Arc<LoadedProgram>>,
) -> Result<LoadedTransaction> {
// NOTE: this check will never fail because `tx` is sanitized
if tx.signatures().is_empty() && fee != 0 {
@ -308,7 +347,7 @@ impl Accounts {
let mut tx_rent: TransactionRent = 0;
let message = tx.message();
let account_keys = message.account_keys();
let mut account_found_and_dep_index = Vec::with_capacity(account_keys.len());
let mut accounts_found = Vec::with_capacity(account_keys.len());
let mut account_deps = Vec::with_capacity(account_keys.len());
let mut rent_debits = RentDebits::default();
@ -319,12 +358,18 @@ impl Accounts {
Self::get_requested_loaded_accounts_data_size_limit(tx, feature_set)?;
let mut accumulated_accounts_data_size: usize = 0;
let instruction_accounts = message
.instructions()
.iter()
.flat_map(|instruction| &instruction.accounts)
.unique()
.collect::<Vec<&u8>>();
let mut accounts = account_keys
.iter()
.enumerate()
.map(|(i, key)| {
let mut account_found = true;
let mut account_dep_index = None;
#[allow(clippy::collapsible_else_if)]
let account = if solana_sdk::sysvar::instructions::check_id(key) {
Self::construct_instructions_account(
@ -333,10 +378,30 @@ impl Accounts {
.is_active(&feature_set::instructions_sysvar_owned_by_sysvar::id()),
)
} else {
let (mut account, rent) = if let Some(account_override) =
let instruction_account = u8::try_from(i)
.map(|i| instruction_accounts.contains(&&i))
.unwrap_or(false);
let (account_size, mut account, rent) = if let Some(account_override) =
account_overrides.and_then(|overrides| overrides.get(key))
{
(account_override.clone(), 0)
(account_override.data().len(), account_override.clone(), 0)
} else if let Some(program) = (!instruction_account && !message.is_writable(i))
.then_some(())
.and_then(|_| loaded_programs.get(key))
{
// This condition block does special handling for accounts that are passed
// as instruction account to any of the instructions in the transaction.
// It's been noticed that some programs are reading other program accounts
// (that are passed to the program as instruction accounts). So such accounts
// are needed to be loaded even though corresponding compiled program may
// already be present in the cache.
Self::account_shared_data_from_program(
key,
feature_set,
program,
program_accounts,
)
.map(|program_account| (program.account_size, program_account, 0))?
} else {
self.accounts_db
.load_with_fixed_root(ancestors, key)
@ -350,9 +415,9 @@ impl Accounts {
set_exempt_rent_epoch_max,
)
.rent_amount;
(account, rent_due)
(account.data().len(), account, rent_due)
} else {
(account, 0)
(account.data().len(), account, 0)
}
})
.unwrap_or_else(|| {
@ -364,12 +429,12 @@ impl Accounts {
// with this field already set would allow us to skip rent collection for these accounts.
default_account.set_rent_epoch(u64::MAX);
}
(default_account, 0)
(default_account.data().len(), default_account, 0)
})
};
Self::accumulate_and_check_loaded_account_data_size(
&mut accumulated_accounts_data_size,
account.data().len(),
account_size,
requested_loaded_accounts_data_size_limit,
error_counters,
)?;
@ -397,36 +462,6 @@ impl Accounts {
error_counters.invalid_writable_account += 1;
return Err(TransactionError::InvalidWritableAccount);
}
if account.executable() {
// The upgradeable loader requires the derived ProgramData account
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = account.state()
{
if let Some((programdata_account, _)) = self
.accounts_db
.load_with_fixed_root(ancestors, &programdata_address)
{
Self::accumulate_and_check_loaded_account_data_size(
&mut accumulated_accounts_data_size,
programdata_account.data().len(),
requested_loaded_accounts_data_size_limit,
error_counters,
)?;
account_dep_index =
Some(account_keys.len().saturating_add(account_deps.len())
as IndexOfAccount);
account_deps.push((programdata_address, programdata_account));
} else {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
} else {
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
}
}
} else if account.executable() && message.is_writable(i) {
error_counters.invalid_writable_account += 1;
return Err(TransactionError::InvalidWritableAccount);
@ -438,7 +473,7 @@ impl Accounts {
account
};
account_found_and_dep_index.push((account_found, account_dep_index));
accounts_found.push(account_found);
Ok((*key, account))
})
.collect::<Result<Vec<_>>>()?;
@ -468,9 +503,7 @@ impl Accounts {
let (program_id, program_account) = accounts
.get(program_index)
.ok_or(TransactionError::ProgramAccountNotFound)?;
let (account_found, account_dep_index) = account_found_and_dep_index
.get(program_index)
.unwrap_or(&(true, None));
let account_found = accounts_found.get(program_index).unwrap_or(&true);
if native_loader::check_id(program_id) {
return Ok(account_indices);
}
@ -483,9 +516,6 @@ impl Accounts {
return Err(TransactionError::InvalidProgramForExecution);
}
account_indices.insert(0, program_index as IndexOfAccount);
if let Some(account_index) = account_dep_index {
account_indices.insert(0, *account_index);
}
let owner_id = program_account.owner();
if native_loader::check_id(owner_id) {
return Ok(account_indices);
@ -641,6 +671,8 @@ impl Accounts {
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
account_overrides: Option<&AccountOverrides>,
program_accounts: &HashMap<Pubkey, &Pubkey>,
loaded_programs: &HashMap<Pubkey, Arc<LoadedProgram>>,
) -> Vec<TransactionLoadResult> {
txs.iter()
.zip(lock_results)
@ -676,6 +708,8 @@ impl Accounts {
rent_collector,
feature_set,
account_overrides,
program_accounts,
loaded_programs,
) {
Ok(loaded_transaction) => loaded_transaction,
Err(e) => return (Err(e), None),
@ -1416,6 +1450,7 @@ mod tests {
solana_program_runtime::executor_cache::TransactionExecutorCache,
solana_sdk::{
account::{AccountSharedData, WritableAccount},
bpf_loader_upgradeable::UpgradeableLoaderState,
epoch_schedule::EpochSchedule,
genesis_config::ClusterType,
hash::Hash,
@ -1500,6 +1535,8 @@ mod tests {
feature_set,
fee_structure,
None,
&HashMap::new(),
&HashMap::new(),
)
}
@ -2542,10 +2579,6 @@ mod tests {
);
assert_eq!(
result.accounts[result.program_indices[0][1] as usize],
accounts[4]
);
assert_eq!(
result.accounts[result.program_indices[0][2] as usize],
accounts[3]
);
}
@ -3272,6 +3305,8 @@ mod tests {
&FeatureSet::all_enabled(),
&FeeStructure::default(),
account_overrides,
&HashMap::new(),
&HashMap::new(),
)
}

View File

@ -95,7 +95,9 @@ use {
compute_budget::{self, ComputeBudget},
executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS},
invoke_context::{BuiltinProgram, ProcessInstructionWithContext},
loaded_programs::{LoadedProgram, LoadedProgramEntry, LoadedPrograms, WorkingSlot},
loaded_programs::{
LoadedProgram, LoadedProgramEntry, LoadedProgramType, LoadedPrograms, WorkingSlot,
},
log_collector::LogCollector,
sysvar_cache::SysvarCache,
timings::{ExecuteTimingType, ExecuteTimings},
@ -4043,6 +4045,7 @@ impl Bank {
}
/// Get any cached executors needed by the transaction
#[cfg(test)]
fn get_tx_executor_cache(
&self,
accounts: &[TransactionAccount],
@ -4197,15 +4200,8 @@ impl Bank {
timings: &mut ExecuteTimings,
error_counters: &mut TransactionErrorMetrics,
log_messages_bytes_limit: Option<usize>,
tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
) -> TransactionExecutionResult {
let mut get_tx_executor_cache_time = Measure::start("get_tx_executor_cache_time");
let tx_executor_cache = self.get_tx_executor_cache(&loaded_transaction.accounts);
get_tx_executor_cache_time.stop();
saturating_add_assign!(
timings.execute_accessories.get_executors_us,
get_tx_executor_cache_time.as_us()
);
let prev_accounts_data_len = self.load_accounts_data_size();
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
let mut transaction_context = TransactionContext::new(
@ -4439,11 +4435,13 @@ impl Bank {
Err(e) => {
// Create a tombstone for the program in the cache
debug!("Failed to load program {}, error {:?}", pubkey, e);
let tombstone = self
.loaded_programs_cache
.write()
.unwrap()
.assign_program(*pubkey, Arc::new(LoadedProgram::new_tombstone(self.slot)));
let tombstone = self.loaded_programs_cache.write().unwrap().assign_program(
*pubkey,
Arc::new(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::FailedVerification,
)),
);
loaded_programs_for_txs.insert(*pubkey, tombstone);
}
});
@ -4451,11 +4449,14 @@ impl Bank {
(program_accounts_map, loaded_programs_for_txs)
}
fn replenish_executor_cache(
fn replenish_executor_cache<'a>(
&self,
program_owners: &[&Pubkey],
program_owners: &[&'a Pubkey],
sanitized_txs: &[SanitizedTransaction],
check_results: &mut [TransactionCheckResult],
) -> (
HashMap<Pubkey, &'a Pubkey>,
HashMap<Pubkey, Arc<LoadedProgram>>,
) {
let mut filter_programs_time = Measure::start("filter_programs_accounts");
let program_accounts_map = self.rc.accounts.filter_executable_program_accounts(
@ -4467,6 +4468,7 @@ impl Bank {
);
filter_programs_time.stop();
let mut loaded_programs_for_txs = HashMap::new();
let mut filter_missing_programs_time = Measure::start("filter_missing_programs_accounts");
let missing_executors = program_accounts_map
.keys()
@ -4475,6 +4477,10 @@ impl Bank {
.read()
.unwrap()
.get(key)
.map(|program| {
loaded_programs_for_txs.insert(*key, program.clone());
program
})
.is_none()
.then_some(key)
})
@ -4484,15 +4490,27 @@ impl Bank {
let executors = missing_executors
.iter()
.map(|pubkey| match self.load_program(pubkey) {
Ok(program) => (**pubkey, program),
Ok(program) => {
loaded_programs_for_txs.insert(**pubkey, program.clone());
(**pubkey, program)
}
// Create a tombstone for the programs that failed to load
Err(_) => (**pubkey, Arc::new(LoadedProgram::new_tombstone(self.slot))),
Err(_) => {
let tombstone = Arc::new(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::FailedVerification,
));
loaded_programs_for_txs.insert(**pubkey, tombstone.clone());
(**pubkey, tombstone)
}
});
// avoid locking the cache if there are no new executors
if executors.len() > 0 {
self.executor_cache.write().unwrap().put(executors);
}
(program_accounts_map, loaded_programs_for_txs)
}
#[allow(clippy::type_complexity)]
@ -4561,7 +4579,6 @@ impl Bank {
bpf_loader_upgradeable::id(),
bpf_loader::id(),
bpf_loader_deprecated::id(),
native_loader::id(),
];
let program_owners_refs: Vec<&Pubkey> = program_owners.iter().collect();
@ -4574,7 +4591,12 @@ impl Bank {
&check_results,
);
*/
self.replenish_executor_cache(&program_owners_refs, sanitized_txs, &mut check_results);
let (executable_programs_in_tx_batch, loaded_programs_map) =
self.replenish_executor_cache(&program_owners_refs, sanitized_txs, &mut check_results);
let tx_executor_cache = Rc::new(RefCell::new(TransactionExecutorCache::new(
loaded_programs_map.clone().into_iter(),
)));
let mut load_time = Measure::start("accounts_load");
let mut loaded_transactions = self.rc.accounts.load_accounts(
@ -4587,6 +4609,8 @@ impl Bank {
&self.feature_set,
&self.fee_structure,
account_overrides,
&executable_programs_in_tx_batch,
&loaded_programs_map,
);
load_time.stop();
@ -4643,6 +4667,7 @@ impl Bank {
timings,
&mut error_counters,
log_messages_bytes_limit,
tx_executor_cache.clone(),
)
}
})

View File

@ -11740,6 +11740,8 @@ fn test_rent_state_list_len() {
&bank.feature_set,
&FeeStructure::default(),
None,
&HashMap::new(),
&HashMap::new(),
);
let compute_budget = bank.runtime_config.compute_budget.unwrap_or_else(|| {