SVM: Split transaction processing code into its own struct (#35044)

* Split transaction processing code into its own struct

* define and implement callback trait
This commit is contained in:
Pankaj Garg 2024-02-04 08:07:39 -08:00 committed by GitHub
parent 9935c2b5e7
commit cb0f13ef07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 397 additions and 205 deletions

View File

@ -86,6 +86,7 @@ use {
AccountShrinkThreshold, AccountStorageEntry, AccountsDb, AccountsDbConfig,
CalcAccountsHashDataSource, VerifyAccountsHashAndLamportsConfig,
},
accounts_file::MatchAccountOwnerError,
accounts_hash::{
AccountHash, AccountsHash, CalcAccountsHashConfig, HashStats, IncrementalAccountsHash,
},
@ -576,15 +577,14 @@ impl PartialEq for Bank {
freeze_started: _,
vote_only_bank: _,
cost_tracker: _,
sysvar_cache: _,
accounts_data_size_initial: _,
accounts_data_size_delta_on_chain: _,
accounts_data_size_delta_off_chain: _,
fee_structure: _,
incremental_snapshot_persistence: _,
loaded_programs_cache: _,
check_program_modification_slot: _,
epoch_reward_status: _,
transaction_processor: _,
// Ignore new fields explicitly if they do not impact PartialEq.
// Adding ".." will remove compile-time checks that if a new field
// is added to the struct, this PartialEq is accordingly updated.
@ -824,8 +824,6 @@ pub struct Bank {
cost_tracker: RwLock<CostTracker>,
sysvar_cache: RwLock<SysvarCache>,
/// The initial accounts data size at the start of this Bank, before processing any transactions/etc
accounts_data_size_initial: u64,
/// The change to accounts data size in this Bank, due on-chain events (i.e. transactions)
@ -844,9 +842,9 @@ pub struct Bank {
pub loaded_programs_cache: Arc<RwLock<LoadedPrograms<BankForks>>>,
pub check_program_modification_slot: bool,
epoch_reward_status: EpochRewardStatus,
transaction_processor: TransactionBatchProcessor,
}
struct VoteWithStakeDelegations {
@ -1026,7 +1024,6 @@ impl Bank {
freeze_started: AtomicBool::default(),
vote_only_bank: false,
cost_tracker: RwLock::<CostTracker>::default(),
sysvar_cache: RwLock::<SysvarCache>::default(),
accounts_data_size_initial: 0,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
@ -1035,10 +1032,12 @@ impl Bank {
Slot::default(),
Epoch::default(),
))),
check_program_modification_slot: false,
epoch_reward_status: EpochRewardStatus::default(),
transaction_processor: TransactionBatchProcessor::default(),
};
bank.transaction_processor = TransactionBatchProcessor::new(&bank);
let accounts_data_size_initial = bank.get_total_accounts_stats().unwrap().data_len as u64;
bank.accounts_data_size_initial = accounts_data_size_initial;
@ -1339,16 +1338,17 @@ impl Bank {
)),
freeze_started: AtomicBool::new(false),
cost_tracker: RwLock::new(CostTracker::default()),
sysvar_cache: RwLock::new(SysvarCache::default()),
accounts_data_size_initial,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
fee_structure: parent.fee_structure.clone(),
loaded_programs_cache: parent.loaded_programs_cache.clone(),
check_program_modification_slot: false,
epoch_reward_status: parent.epoch_reward_status.clone(),
transaction_processor: TransactionBatchProcessor::default(),
};
new.transaction_processor = TransactionBatchProcessor::new(&new);
let (_, ancestors_time_us) = measure_us!({
let mut ancestors = Vec::with_capacity(1 + new.parents().len());
ancestors.push(new.slot());
@ -1843,7 +1843,6 @@ impl Bank {
freeze_started: AtomicBool::new(fields.hash != Hash::default()),
vote_only_bank: false,
cost_tracker: RwLock::new(CostTracker::default()),
sysvar_cache: RwLock::new(SysvarCache::default()),
accounts_data_size_initial,
accounts_data_size_delta_on_chain: AtomicI64::new(0),
accounts_data_size_delta_off_chain: AtomicI64::new(0),
@ -1852,9 +1851,12 @@ impl Bank {
fields.slot,
fields.epoch,
))),
check_program_modification_slot: false,
epoch_reward_status: fields.epoch_reward_status,
transaction_processor: TransactionBatchProcessor::default(),
};
bank.transaction_processor = TransactionBatchProcessor::new(&bank);
bank.finish_init(
genesis_config,
additional_builtins,
@ -3340,7 +3342,7 @@ impl Bank {
let pre_lamport = curr_stake_account.lamports();
let post_lamport = post_stake_account.lamports();
assert_eq!(pre_lamport + u64::try_from(reward_amount).unwrap(), post_lamport,
"stake account balance has changed since the reward calculation! account: {stake_pubkey}, pre balance: {pre_lamport}, post balance: {post_lamport}, rewards: {reward_amount}");
"stake account balance has changed since the reward calculation! account: {stake_pubkey}, pre balance: {pre_lamport}, post balance: {post_lamport}, rewards: {reward_amount}");
}
}
}
@ -4588,18 +4590,24 @@ impl Bank {
}
balances
}
}
fn program_modification_slot(&self, pubkey: &Pubkey) -> Result<Slot> {
let program = self
.get_account_with_fixed_root(pubkey)
impl TransactionBatchProcessor {
fn program_modification_slot<CB: TransactionProcessingCallback>(
&self,
callbacks: &CB,
pubkey: &Pubkey,
) -> Result<Slot> {
let program = callbacks
.get_account_shared_data(pubkey)
.ok_or(TransactionError::ProgramAccountNotFound)?;
if bpf_loader_upgradeable::check_id(program.owner()) {
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = program.state()
{
let programdata = self
.get_account_with_fixed_root(&programdata_address)
let programdata = callbacks
.get_account_shared_data(&programdata_address)
.ok_or(TransactionError::ProgramAccountNotFound)?;
if let Ok(UpgradeableLoaderState::ProgramData {
slot,
@ -4619,12 +4627,13 @@ impl Bank {
}
}
fn load_program_accounts(
fn load_program_accounts<CB: TransactionProcessingCallback>(
&self,
callbacks: &CB,
pubkey: &Pubkey,
environments: &ProgramRuntimeEnvironments,
) -> ProgramAccountLoadResult {
let program_account = match self.get_account_with_fixed_root(pubkey) {
let program_account = match callbacks.get_account_shared_data(pubkey) {
None => return ProgramAccountLoadResult::AccountNotFound,
Some(account) => account,
};
@ -4653,7 +4662,8 @@ impl Bank {
programdata_address,
}) = program_account.state()
{
let programdata_account = match self.get_account_with_fixed_root(&programdata_address) {
let programdata_account = match callbacks.get_account_shared_data(&programdata_address)
{
None => return ProgramAccountLoadResult::AccountNotFound,
Some(account) => account,
};
@ -4710,8 +4720,9 @@ impl Bank {
}
}
pub fn load_program(
pub fn load_program<CB: TransactionProcessingCallback>(
&self,
callbacks: &CB,
pubkey: &Pubkey,
reload: bool,
recompile: Option<Arc<LoadedProgram>>,
@ -4728,100 +4739,107 @@ impl Bank {
..LoadProgramMetrics::default()
};
let mut loaded_program = match self.load_program_accounts(pubkey, environments) {
ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::Closed,
)),
let mut loaded_program =
match self.load_program_accounts(callbacks, pubkey, environments) {
ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::Closed,
)),
ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)),
ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)),
ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => {
Self::load_program_from_bytes(
&mut load_program_metrics,
program_account.data(),
program_account.owner(),
program_account.data().len(),
0,
environments.program_runtime_v1.clone(),
reload,
)
.map_err(|_| (0, environments.program_runtime_v1.clone()))
}
ProgramAccountLoadResult::ProgramOfLoaderV3(
program_account,
programdata_account,
slot,
) => programdata_account
.data()
.get(UpgradeableLoaderState::size_of_programdata_metadata()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|programdata| {
ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => {
Self::load_program_from_bytes(
&mut load_program_metrics,
programdata,
program_account.data(),
program_account.owner(),
program_account
.data()
.len()
.saturating_add(programdata_account.data().len()),
slot,
program_account.data().len(),
0,
environments.program_runtime_v1.clone(),
reload,
)
})
.map_err(|_| (slot, environments.program_runtime_v1.clone())),
.map_err(|_| (0, environments.program_runtime_v1.clone()))
}
ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => program_account
.data()
.get(LoaderV4State::program_data_offset()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|elf_bytes| {
Self::load_program_from_bytes(
&mut load_program_metrics,
elf_bytes,
&loader_v4::id(),
program_account.data().len(),
slot,
environments.program_runtime_v2.clone(),
reload,
)
})
.map_err(|_| (slot, environments.program_runtime_v2.clone())),
}
.unwrap_or_else(|(slot, env)| {
LoadedProgram::new_tombstone(slot, LoadedProgramType::FailedVerification(env))
});
ProgramAccountLoadResult::ProgramOfLoaderV3(
program_account,
programdata_account,
slot,
) => programdata_account
.data()
.get(UpgradeableLoaderState::size_of_programdata_metadata()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|programdata| {
Self::load_program_from_bytes(
&mut load_program_metrics,
programdata,
program_account.owner(),
program_account
.data()
.len()
.saturating_add(programdata_account.data().len()),
slot,
environments.program_runtime_v1.clone(),
reload,
)
})
.map_err(|_| (slot, environments.program_runtime_v1.clone())),
ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => {
program_account
.data()
.get(LoaderV4State::program_data_offset()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|elf_bytes| {
Self::load_program_from_bytes(
&mut load_program_metrics,
elf_bytes,
&loader_v4::id(),
program_account.data().len(),
slot,
environments.program_runtime_v2.clone(),
reload,
)
})
.map_err(|_| (slot, environments.program_runtime_v2.clone()))
}
}
.unwrap_or_else(|(slot, env)| {
LoadedProgram::new_tombstone(slot, LoadedProgramType::FailedVerification(env))
});
let mut timings = ExecuteDetailsTimings::default();
load_program_metrics.submit_datapoint(&mut timings);
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.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 =
AtomicU64::new(recompile.ix_usage_counter.load(Ordering::Relaxed));
}
loaded_program.update_access_slot(self.slot());
loaded_program.update_access_slot(self.slot);
Arc::new(loaded_program)
}
}
impl Bank {
pub fn clear_program_cache(&self) {
self.loaded_programs_cache
.write()
.unwrap()
.unload_all_programs();
}
}
impl TransactionBatchProcessor {
/// Execute a transaction using the provided loaded accounts and update
/// the executors cache if the transaction was successful.
#[allow(clippy::too_many_arguments)]
fn execute_loaded_transaction(
fn execute_loaded_transaction<CB: TransactionProcessingCallback>(
&self,
callback: &CB,
tx: &SanitizedTransaction,
loaded_transaction: &mut LoadedTransaction,
compute_budget: ComputeBudget,
@ -4853,7 +4871,7 @@ impl Bank {
let mut transaction_context = TransactionContext::new(
transaction_accounts,
self.rent_collector.rent.clone(),
callback.get_rent_collector().rent.clone(),
compute_budget.max_invoke_stack_height,
compute_budget.max_instruction_trace_length,
);
@ -4861,7 +4879,7 @@ impl Bank {
transaction_context.set_signature(tx.signature());
let pre_account_state_info = TransactionAccountStateInfo::new(
&self.rent_collector.rent,
&callback.get_rent_collector().rent,
&transaction_context,
tx.message(),
);
@ -4877,7 +4895,8 @@ impl Bank {
None
};
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
let (blockhash, lamports_per_signature) =
callback.get_last_blockhash_and_lamports_per_signature();
let mut executed_units = 0u64;
let mut programs_modified_by_tx = LoadedProgramsForTxBatch::new(
@ -4892,7 +4911,7 @@ impl Bank {
log_collector.clone(),
programs_loaded_for_tx_batch,
&mut programs_modified_by_tx,
self.feature_set.clone(),
callback.get_feature_set(),
compute_budget,
timings,
&self.sysvar_cache.read().unwrap(),
@ -4910,7 +4929,7 @@ impl Bank {
let mut status = process_result
.and_then(|info| {
let post_account_state_info = TransactionAccountStateInfo::new(
&self.rent_collector.rent,
&callback.get_rent_collector().rent,
&transaction_context,
tx.message(),
);
@ -4995,8 +5014,9 @@ impl Bank {
}
}
fn replenish_program_cache(
fn replenish_program_cache<CB: TransactionProcessingCallback>(
&self,
callback: &CB,
program_accounts_map: &HashMap<Pubkey, (&Pubkey, u64)>,
) -> LoadedProgramsForTxBatch {
let mut missing_programs: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> =
@ -5007,7 +5027,7 @@ impl Bank {
(
*pubkey,
(
self.program_modification_slot(pubkey)
self.program_modification_slot(callback, pubkey)
.map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
}),
@ -5043,11 +5063,7 @@ impl Bank {
}
// Submit our last completed loading task.
if let Some((key, program)) = program_to_store.take() {
loaded_programs_cache.finish_cooperative_loading_task(
self.slot(),
key,
program,
);
loaded_programs_cache.finish_cooperative_loading_task(self.slot, key, program);
}
// Figure out which program needs to be loaded next.
let program_to_load = loaded_programs_cache.extract(
@ -5062,7 +5078,7 @@ impl Bank {
if let Some((key, count)) = program_to_load {
// Load, verify and compile one program.
let program = self.load_program(&key, false, None);
let program = self.load_program(callback, &key, false, None);
program.tx_usage_counter.store(count, Ordering::Relaxed);
program_to_store = Some((key, program));
} else if missing_programs.is_empty() {
@ -5081,9 +5097,9 @@ impl Bank {
/// Returns a hash map of executable program accounts (program accounts that are not writable
/// in the given transactions), and their owners, for the transactions with a valid
/// blockhash or nonce.
fn filter_executable_program_accounts<'a>(
fn filter_executable_program_accounts<'a, CB: TransactionProcessingCallback>(
&self,
ancestors: &Ancestors,
callbacks: &CB,
txs: &[SanitizedTransaction],
lock_results: &mut [TransactionCheckResult],
program_owners: &'a [Pubkey],
@ -5101,11 +5117,8 @@ impl Bank {
saturating_add_assign!(*count, 1);
}
Entry::Vacant(entry) => {
if let Ok(index) = self
.rc
.accounts
.accounts_db
.account_matches_owners(ancestors, key, program_owners)
if let Ok(index) =
callbacks.account_matches_owners(key, program_owners)
{
program_owners
.get(index)
@ -5123,7 +5136,9 @@ impl Bank {
});
result
}
}
impl Bank {
#[allow(clippy::type_complexity)]
pub fn load_and_execute_transactions(
&self,
@ -5187,17 +5202,21 @@ impl Bank {
debug!("check: {}us", check_time.as_us());
timings.saturating_add_in_place(ExecuteTimingType::CheckUs, check_time.as_us());
let sanitized_output = self.load_and_execute_sanitized_transactions(
sanitized_txs,
&mut check_results,
&mut error_counters,
enable_cpi_recording,
enable_log_recording,
enable_return_data_recording,
timings,
account_overrides,
log_messages_bytes_limit,
);
let sanitized_output = self
.transaction_processor
.load_and_execute_sanitized_transactions(
self,
sanitized_txs,
&mut check_results,
&mut error_counters,
enable_cpi_recording,
enable_log_recording,
enable_return_data_recording,
timings,
account_overrides,
self.builtin_programs.iter(),
log_messages_bytes_limit,
);
let mut signature_count = 0;
@ -5325,10 +5344,13 @@ impl Bank {
error_counters,
}
}
}
impl TransactionBatchProcessor {
#[allow(clippy::too_many_arguments)]
fn load_and_execute_sanitized_transactions(
fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>(
&self,
callbacks: &CB,
sanitized_txs: &[SanitizedTransaction],
check_results: &mut [TransactionCheckResult],
error_counters: &mut TransactionErrorMetrics,
@ -5337,35 +5359,32 @@ impl Bank {
enable_return_data_recording: bool,
timings: &mut ExecuteTimings,
account_overrides: Option<&AccountOverrides>,
builtin_programs: impl Iterator<Item = &'a Pubkey>,
log_messages_bytes_limit: Option<usize>,
) -> LoadAndExecuteSanitizedTransactionsOutput {
let mut program_accounts_map = self.filter_executable_program_accounts(
&self.ancestors,
callbacks,
sanitized_txs,
check_results,
PROGRAM_OWNERS,
);
let native_loader = native_loader::id();
for builtin_program in self.builtin_programs.iter() {
for builtin_program in builtin_programs {
program_accounts_map.insert(*builtin_program, (&native_loader, 0));
}
let programs_loaded_for_tx_batch = Rc::new(RefCell::new(
self.replenish_program_cache(&program_accounts_map),
self.replenish_program_cache(callbacks, &program_accounts_map),
));
let mut load_time = Measure::start("accounts_load");
let mut loaded_transactions = load_accounts(
&self.rc.accounts.accounts_db,
&self.ancestors,
callbacks,
sanitized_txs,
check_results,
error_counters,
&self.rent_collector,
&self.feature_set,
&self.fee_structure,
account_overrides,
self.get_reward_interval(),
&program_accounts_map,
&programs_loaded_for_tx_batch.borrow(),
);
@ -5402,6 +5421,7 @@ impl Bank {
};
let result = self.execute_loaded_transaction(
callbacks,
tx,
loaded_transaction,
compute_budget,
@ -5442,7 +5462,7 @@ impl Bank {
.unwrap()
.evict_using_2s_random_selection(
Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
self.slot(),
self.slot,
);
debug!(
@ -5460,7 +5480,9 @@ impl Bank {
execution_results,
}
}
}
impl Bank {
/// Load the accounts data size, in bytes
pub fn load_accounts_data_size(&self) -> u64 {
self.accounts_data_size_initial
@ -8190,7 +8212,7 @@ impl Bank {
pub fn is_in_slot_hashes_history(&self, slot: &Slot) -> bool {
if slot < &self.slot {
if let Ok(sysvar_cache) = self.sysvar_cache.read() {
if let Ok(sysvar_cache) = self.transaction_processor.sysvar_cache.read() {
if let Ok(slot_hashes) = sysvar_cache.get_slot_hashes() {
return slot_hashes.get(slot).is_some();
}
@ -8198,6 +8220,156 @@ impl Bank {
}
false
}
pub fn check_program_modification_slot(&mut self) {
self.transaction_processor.check_program_modification_slot = true;
}
pub fn load_program(
&self,
pubkey: &Pubkey,
reload: bool,
recompile: Option<Arc<LoadedProgram>>,
) -> Arc<LoadedProgram> {
self.transaction_processor
.load_program(self, pubkey, reload, recompile)
}
}
pub trait TransactionProcessingCallback {
fn account_matches_owners(
&self,
account: &Pubkey,
owners: &[Pubkey],
) -> std::result::Result<usize, MatchAccountOwnerError>;
fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData>;
fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64);
fn get_rent_collector(&self) -> &RentCollector;
fn get_feature_set(&self) -> Arc<FeatureSet>;
fn check_account_access(
&self,
_tx: &SanitizedTransaction,
_account_index: usize,
_account: &AccountSharedData,
_error_counters: &mut TransactionErrorMetrics,
) -> Result<()> {
Ok(())
}
}
impl TransactionProcessingCallback for Bank {
fn account_matches_owners(
&self,
account: &Pubkey,
owners: &[Pubkey],
) -> std::result::Result<usize, MatchAccountOwnerError> {
self.rc
.accounts
.accounts_db
.account_matches_owners(&self.ancestors, account, owners)
}
fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.rc
.accounts
.accounts_db
.load_with_fixed_root(&self.ancestors, pubkey)
.map(|(acc, _)| acc)
}
fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) {
self.last_blockhash_and_lamports_per_signature()
}
fn get_rent_collector(&self) -> &RentCollector {
&self.rent_collector
}
fn get_feature_set(&self) -> Arc<FeatureSet> {
self.feature_set.clone()
}
fn check_account_access(
&self,
tx: &SanitizedTransaction,
account_index: usize,
account: &AccountSharedData,
error_counters: &mut TransactionErrorMetrics,
) -> Result<()> {
if self.get_reward_interval() == RewardInterval::InsideInterval
&& tx.message().is_writable(account_index)
&& solana_stake_program::check_id(account.owner())
{
error_counters.program_execution_temporarily_restricted += 1;
Err(TransactionError::ProgramExecutionTemporarilyRestricted {
account_index: account_index as u8,
})
} else {
Ok(())
}
}
}
#[derive(AbiExample, Debug)]
struct TransactionBatchProcessor {
/// Bank slot (i.e. block)
slot: Slot,
/// Bank epoch
epoch: Epoch,
/// initialized from genesis
epoch_schedule: EpochSchedule,
/// Transaction fee structure
fee_structure: FeeStructure,
pub check_program_modification_slot: bool,
/// Optional config parameters that can override runtime behavior
runtime_config: Arc<RuntimeConfig>,
sysvar_cache: RwLock<SysvarCache>,
pub loaded_programs_cache: Arc<RwLock<LoadedPrograms<BankForks>>>,
}
impl Default for TransactionBatchProcessor {
fn default() -> Self {
Self {
slot: Slot::default(),
epoch: Epoch::default(),
epoch_schedule: EpochSchedule::default(),
fee_structure: FeeStructure::default(),
check_program_modification_slot: false,
runtime_config: Arc::<RuntimeConfig>::default(),
sysvar_cache: RwLock::<SysvarCache>::default(),
loaded_programs_cache: Arc::new(RwLock::new(LoadedPrograms::new(
Slot::default(),
Epoch::default(),
))),
}
}
}
impl TransactionBatchProcessor {
fn new(bank: &Bank) -> Self {
Self {
slot: bank.slot(),
epoch: bank.epoch(),
epoch_schedule: bank.epoch_schedule.clone(),
fee_structure: bank.fee_structure.clone(),
check_program_modification_slot: false,
runtime_config: bank.runtime_config.clone(),
sysvar_cache: RwLock::<SysvarCache>::default(),
loaded_programs_cache: bank.loaded_programs_cache.clone(),
}
}
}
#[cfg(feature = "dev-context-only-utils")]

View File

@ -16,6 +16,7 @@ impl AddressLoader for &Bank {
address_table_lookups: &[MessageAddressTableLookup],
) -> Result<LoadedAddresses, AddressLoaderError> {
let slot_hashes = self
.transaction_processor
.sysvar_cache
.read()
.unwrap()

View File

@ -5,7 +5,7 @@ use {
impl Bank {
pub(crate) fn fill_missing_sysvar_cache_entries(&self) {
let mut sysvar_cache = self.sysvar_cache.write().unwrap();
let mut sysvar_cache = self.transaction_processor.sysvar_cache.write().unwrap();
sysvar_cache.fill_missing_entries(|pubkey, callback| {
if let Some(account) = self.get_account_with_fixed_root(pubkey) {
callback(account.data());
@ -14,12 +14,16 @@ impl Bank {
}
pub(crate) fn reset_sysvar_cache(&self) {
let mut sysvar_cache = self.sysvar_cache.write().unwrap();
let mut sysvar_cache = self.transaction_processor.sysvar_cache.write().unwrap();
sysvar_cache.reset();
}
pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
self.sysvar_cache.read().unwrap().clone()
self.transaction_processor
.sysvar_cache
.read()
.unwrap()
.clone()
}
}
@ -40,7 +44,7 @@ mod tests {
let (genesis_config, _mint_keypair) = create_genesis_config(100_000);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
let bank0_sysvar_cache = bank0.sysvar_cache.read().unwrap();
let bank0_sysvar_cache = bank0.transaction_processor.sysvar_cache.read().unwrap();
let bank0_cached_clock = bank0_sysvar_cache.get_clock();
let bank0_cached_epoch_schedule = bank0_sysvar_cache.get_epoch_schedule();
let bank0_cached_fees = bank0_sysvar_cache.get_fees();
@ -60,7 +64,7 @@ mod tests {
bank1_slot,
));
let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap();
let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap();
let bank1_cached_clock = bank1_sysvar_cache.get_clock();
let bank1_cached_epoch_schedule = bank1_sysvar_cache.get_epoch_schedule();
let bank1_cached_fees = bank1_sysvar_cache.get_fees();
@ -81,7 +85,7 @@ mod tests {
let bank2_slot = bank1.slot() + 1;
let bank2 = Bank::new_from_parent(bank1.clone(), &Pubkey::default(), bank2_slot);
let bank2_sysvar_cache = bank2.sysvar_cache.read().unwrap();
let bank2_sysvar_cache = bank2.transaction_processor.sysvar_cache.read().unwrap();
let bank2_cached_clock = bank2_sysvar_cache.get_clock();
let bank2_cached_epoch_schedule = bank2_sysvar_cache.get_epoch_schedule();
let bank2_cached_fees = bank2_sysvar_cache.get_fees();
@ -112,7 +116,7 @@ mod tests {
let bank1_slot = bank0.slot() + 1;
let mut bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), bank1_slot);
let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap();
let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap();
let bank1_cached_clock = bank1_sysvar_cache.get_clock();
let bank1_cached_epoch_schedule = bank1_sysvar_cache.get_epoch_schedule();
let bank1_cached_fees = bank1_sysvar_cache.get_fees();
@ -130,7 +134,7 @@ mod tests {
drop(bank1_sysvar_cache);
bank1.reset_sysvar_cache();
let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap();
let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap();
assert!(bank1_sysvar_cache.get_clock().is_err());
assert!(bank1_sysvar_cache.get_epoch_schedule().is_err());
assert!(bank1_sysvar_cache.get_fees().is_err());
@ -155,7 +159,7 @@ mod tests {
bank1.fill_missing_sysvar_cache_entries();
let bank1_sysvar_cache = bank1.sysvar_cache.read().unwrap();
let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap();
assert_eq!(bank1_sysvar_cache.get_clock(), bank1_cached_clock);
assert_eq!(
bank1_sysvar_cache.get_epoch_schedule(),

View File

@ -10987,16 +10987,12 @@ fn test_rent_state_list_len() {
let sanitized_tx = SanitizedTransaction::try_from_legacy_transaction(tx).unwrap();
let mut error_counters = TransactionErrorMetrics::default();
let loaded_txs = load_accounts(
&bank.accounts().accounts_db,
&bank.ancestors,
&bank,
&[sanitized_tx.clone()],
&[(Ok(()), None, Some(0))],
&mut error_counters,
&bank.rent_collector,
&bank.feature_set,
&FeeStructure::default(),
None,
RewardInterval::OutsideInterval,
&HashMap::new(),
&LoadedProgramsForTxBatch::default(),
);
@ -13744,10 +13740,10 @@ fn test_filter_executable_program_accounts() {
);
let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2);
let ancestors = vec![(0, 0)].into_iter().collect();
let owners = &[program1_pubkey, program2_pubkey];
let programs = bank.filter_executable_program_accounts(
&ancestors,
let transaction_processor = TransactionBatchProcessor::new(&bank);
let programs = transaction_processor.filter_executable_program_accounts(
&bank,
&[sanitized_tx1, sanitized_tx2],
&mut [(Ok(()), None, Some(0)), (Ok(()), None, Some(0))],
owners,
@ -13839,11 +13835,11 @@ fn test_filter_executable_program_accounts_invalid_blockhash() {
// Let's not register blockhash from tx2. This should cause the tx2 to fail
let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2);
let ancestors = vec![(0, 0)].into_iter().collect();
let owners = &[program1_pubkey, program2_pubkey];
let mut lock_results = vec![(Ok(()), None, Some(0)), (Ok(()), None, None)];
let programs = bank.filter_executable_program_accounts(
&ancestors,
let transaction_processor = TransactionBatchProcessor::new(&bank);
let programs = transaction_processor.filter_executable_program_accounts(
&bank,
&[sanitized_tx1, sanitized_tx2],
&mut lock_results,
owners,

View File

@ -221,8 +221,9 @@ impl BankForks {
}
pub fn insert(&mut self, mut bank: Bank) -> BankWithScheduler {
bank.check_program_modification_slot =
self.root.load(Ordering::Relaxed) < self.highest_slot_at_startup;
if self.root.load(Ordering::Relaxed) < self.highest_slot_at_startup {
bank.check_program_modification_slot();
}
let bank = Arc::new(bank);
let bank = if let Some(scheduler_pool) = &self.scheduler_pool {

View File

@ -1,12 +1,10 @@
use {
crate::{bank::RewardInterval, svm::account_rent_state::RentState},
crate::{bank::TransactionProcessingCallback, svm::account_rent_state::RentState},
itertools::Itertools,
log::warn,
solana_accounts_db::{
account_overrides::AccountOverrides,
accounts::{LoadedTransaction, TransactionLoadResult, TransactionRent},
accounts_db::AccountsDb,
ancestors::Ancestors,
nonce_info::NonceFull,
rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH},
rent_debits::RentDebits,
@ -22,7 +20,7 @@ use {
create_executable_meta, is_builtin, is_executable, Account, AccountSharedData,
ReadableAccount, WritableAccount,
},
feature_set::{self, include_loaded_accounts_data_size_in_fee_calculation, FeatureSet},
feature_set::{self, include_loaded_accounts_data_size_in_fee_calculation},
fee::FeeStructure,
message::SanitizedMessage,
native_loader,
@ -38,21 +36,17 @@ use {
std::{collections::HashMap, num::NonZeroUsize},
};
#[allow(clippy::too_many_arguments)]
pub(crate) fn load_accounts(
accounts_db: &AccountsDb,
ancestors: &Ancestors,
pub(crate) fn load_accounts<CB: TransactionProcessingCallback>(
callbacks: &CB,
txs: &[SanitizedTransaction],
lock_results: &[TransactionCheckResult],
error_counters: &mut TransactionErrorMetrics,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
account_overrides: Option<&AccountOverrides>,
in_reward_interval: RewardInterval,
program_accounts: &HashMap<Pubkey, (&Pubkey, u64)>,
loaded_programs: &LoadedProgramsForTxBatch,
) -> Vec<TransactionLoadResult> {
let feature_set = callbacks.get_feature_set();
txs.iter()
.zip(lock_results)
.map(|etx| match etx {
@ -75,15 +69,11 @@ pub(crate) fn load_accounts(
// load transactions
let loaded_transaction = match load_transaction_accounts(
accounts_db,
ancestors,
callbacks,
tx,
fee,
error_counters,
rent_collector,
feature_set,
account_overrides,
in_reward_interval,
program_accounts,
loaded_programs,
) {
@ -113,27 +103,22 @@ pub(crate) fn load_accounts(
.collect()
}
#[allow(clippy::too_many_arguments)]
fn load_transaction_accounts(
accounts_db: &AccountsDb,
ancestors: &Ancestors,
fn load_transaction_accounts<CB: TransactionProcessingCallback>(
callbacks: &CB,
tx: &SanitizedTransaction,
fee: u64,
error_counters: &mut TransactionErrorMetrics,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
account_overrides: Option<&AccountOverrides>,
reward_interval: RewardInterval,
program_accounts: &HashMap<Pubkey, (&Pubkey, u64)>,
loaded_programs: &LoadedProgramsForTxBatch,
) -> Result<LoadedTransaction> {
let in_reward_interval = reward_interval == RewardInterval::InsideInterval;
// NOTE: this check will never fail because `tx` is sanitized
if tx.signatures().is_empty() && fee != 0 {
return Err(TransactionError::MissingSignatureForFee);
}
let feature_set = callbacks.get_feature_set();
// There is no way to predict what program will execute without an error
// If a fee can pay for execution then the program will be scheduled
let mut validated_fee_payer = false;
@ -143,6 +128,7 @@ fn load_transaction_accounts(
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();
let rent_collector = callbacks.get_rent_collector();
let set_exempt_rent_epoch_max =
feature_set.is_active(&solana_sdk::feature_set::set_exempt_rent_epoch_max::id());
@ -183,9 +169,9 @@ fn load_transaction_accounts(
account_shared_data_from_program(key, program_accounts)
.map(|program_account| (program.account_size, program_account, 0))?
} else {
accounts_db
.load_with_fixed_root(ancestors, key)
.map(|(mut account, _)| {
callbacks
.get_account_shared_data(key)
.map(|mut account| {
if message.is_writable(i) {
if !feature_set
.is_active(&feature_set::disable_rent_fees_collection::id())
@ -253,15 +239,7 @@ fn load_transaction_accounts(
validated_fee_payer = true;
}
if in_reward_interval
&& message.is_writable(i)
&& solana_stake_program::check_id(account.owner())
{
error_counters.program_execution_temporarily_restricted += 1;
return Err(TransactionError::ProgramExecutionTemporarilyRestricted {
account_index: i as u8,
});
}
callbacks.check_account_access(tx, i, &account, error_counters)?;
tx_rent += rent;
rent_debits.insert(key, rent, account.lamports());
@ -306,7 +284,7 @@ fn load_transaction_accounts(
return Err(TransactionError::ProgramAccountNotFound);
}
if !(is_builtin(program_account) || is_executable(program_account, feature_set)) {
if !(is_builtin(program_account) || is_executable(program_account, &feature_set)) {
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
}
@ -324,12 +302,10 @@ fn load_transaction_accounts(
builtins_start_index.saturating_add(owner_index)
} else {
let owner_index = accounts.len();
if let Some((owner_account, _)) =
accounts_db.load_with_fixed_root(ancestors, owner_id)
{
if let Some(owner_account) = callbacks.get_account_shared_data(owner_id) {
if !native_loader::check_id(owner_account.owner())
|| !(is_builtin(&owner_account)
|| is_executable(&owner_account, feature_set))
|| is_executable(&owner_account, &feature_set))
{
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
@ -484,7 +460,10 @@ mod tests {
use {
super::*,
nonce::state::Versions as NonceVersions,
solana_accounts_db::{accounts::Accounts, rent_collector::RentCollector},
solana_accounts_db::{
accounts::Accounts, accounts_db::AccountsDb, accounts_file::MatchAccountOwnerError,
ancestors::Ancestors, rent_collector::RentCollector,
},
solana_program_runtime::{
compute_budget_processor,
prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
@ -494,6 +473,7 @@ mod tests {
bpf_loader_upgradeable,
compute_budget::ComputeBudgetInstruction,
epoch_schedule::EpochSchedule,
feature_set::FeatureSet,
hash::Hash,
instruction::CompiledInstruction,
message::{Message, SanitizedMessage},
@ -507,6 +487,41 @@ mod tests {
std::{convert::TryFrom, sync::Arc},
};
struct TestCallbacks {
accounts: Accounts,
ancestors: Ancestors,
rent_collector: RentCollector,
feature_set: Arc<FeatureSet>,
}
impl TransactionProcessingCallback for TestCallbacks {
fn account_matches_owners(
&self,
_account: &Pubkey,
_owners: &[Pubkey],
) -> std::result::Result<usize, MatchAccountOwnerError> {
Err(MatchAccountOwnerError::UnableToLoad)
}
fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.accounts
.load_without_fixed_root(&self.ancestors, pubkey)
.map(|(acc, _slot)| acc)
}
fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) {
(Hash::new_unique(), 0)
}
fn get_rent_collector(&self) -> &RentCollector {
&self.rent_collector
}
fn get_feature_set(&self) -> Arc<FeatureSet> {
self.feature_set.clone()
}
}
fn load_accounts_with_fee_and_rent(
tx: Transaction,
ka: &[TransactionAccount],
@ -525,17 +540,19 @@ mod tests {
let ancestors = vec![(0, 0)].into_iter().collect();
feature_set.deactivate(&feature_set::disable_rent_fees_collection::id());
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx);
let callbacks = TestCallbacks {
accounts,
ancestors,
rent_collector: rent_collector.clone(),
feature_set: Arc::new(feature_set.clone()),
};
load_accounts(
&accounts.accounts_db,
&ancestors,
&callbacks,
&[sanitized_tx],
&[(Ok(()), None, Some(lamports_per_signature))],
error_counters,
rent_collector,
feature_set,
fee_structure,
None,
RewardInterval::OutsideInterval,
&HashMap::new(),
&LoadedProgramsForTxBatch::default(),
)
@ -990,26 +1007,27 @@ mod tests {
}
fn load_accounts_no_store(
accounts: &Accounts,
accounts: Accounts,
tx: Transaction,
account_overrides: Option<&AccountOverrides>,
) -> Vec<TransactionLoadResult> {
let tx = SanitizedTransaction::from_transaction_for_tests(tx);
let rent_collector = RentCollector::default();
let ancestors = vec![(0, 0)].into_iter().collect();
let mut error_counters = TransactionErrorMetrics::default();
let callbacks = TestCallbacks {
accounts,
ancestors,
rent_collector: RentCollector::default(),
feature_set: Arc::new(FeatureSet::all_enabled()),
};
load_accounts(
&accounts.accounts_db,
&ancestors,
&callbacks,
&[tx],
&[(Ok(()), None, Some(10))],
&mut error_counters,
&rent_collector,
&FeatureSet::all_enabled(),
&FeeStructure::default(),
account_overrides,
RewardInterval::OutsideInterval,
&HashMap::new(),
&LoadedProgramsForTxBatch::default(),
)
@ -1032,7 +1050,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx, None);
let loaded_accounts = load_accounts_no_store(accounts, tx, None);
assert_eq!(loaded_accounts.len(), 1);
assert!(loaded_accounts[0].0.is_err());
}
@ -1060,7 +1078,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx, Some(&account_overrides));
let loaded_accounts = load_accounts_no_store(accounts, tx, Some(&account_overrides));
assert_eq!(loaded_accounts.len(), 1);
let loaded_transaction = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(loaded_transaction.accounts[0].0, keypair.pubkey());