SVM: Move transaction processing code out of `bank.rs` (#35075)
SVM: Move transaction processing code out of bank.rs
This commit is contained in:
parent
a16f982169
commit
116119cfd2
|
@ -59,9 +59,8 @@ use {
|
|||
},
|
||||
stakes::{InvalidCacheEntryReason, Stakes, StakesCache, StakesEnum},
|
||||
status_cache::{SlotDelta, StatusCache},
|
||||
svm::{
|
||||
account_loader::load_accounts,
|
||||
transaction_account_state_info::TransactionAccountStateInfo,
|
||||
svm::transaction_processor::{
|
||||
TransactionBatchProcessor, TransactionLogMessages, TransactionProcessingCallback,
|
||||
},
|
||||
transaction_batch::TransactionBatch,
|
||||
},
|
||||
|
@ -69,7 +68,6 @@ use {
|
|||
dashmap::{DashMap, DashSet},
|
||||
itertools::izip,
|
||||
log::*,
|
||||
percentage::Percentage,
|
||||
rayon::{
|
||||
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
|
||||
slice::ParallelSlice,
|
||||
|
@ -78,10 +76,7 @@ use {
|
|||
serde::Serialize,
|
||||
solana_accounts_db::{
|
||||
account_overrides::AccountOverrides,
|
||||
accounts::{
|
||||
AccountAddressFilter, Accounts, LoadedTransaction, PubkeyAccountSlot,
|
||||
TransactionLoadResult,
|
||||
},
|
||||
accounts::{AccountAddressFilter, Accounts, PubkeyAccountSlot, TransactionLoadResult},
|
||||
accounts_db::{
|
||||
AccountShrinkThreshold, AccountStorageEntry, AccountsDb, AccountsDbConfig,
|
||||
CalcAccountsHashDataSource, VerifyAccountsHashAndLamportsConfig,
|
||||
|
@ -105,7 +100,6 @@ use {
|
|||
storable_accounts::StorableAccounts,
|
||||
transaction_error_metrics::TransactionErrorMetrics,
|
||||
transaction_results::{
|
||||
inner_instructions_list_from_instruction_trace, DurableNonceFee,
|
||||
TransactionCheckResult, TransactionExecutionDetails, TransactionExecutionResult,
|
||||
TransactionResults,
|
||||
},
|
||||
|
@ -116,27 +110,17 @@ use {
|
|||
solana_measure::{measure, measure::Measure, measure_us},
|
||||
solana_perf::perf_libs,
|
||||
solana_program_runtime::{
|
||||
compute_budget::ComputeBudget,
|
||||
compute_budget_processor::process_compute_budget_instructions,
|
||||
invoke_context::BuiltinFunctionWithContext,
|
||||
loaded_programs::{
|
||||
LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
|
||||
LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
|
||||
ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
|
||||
},
|
||||
log_collector::LogCollector,
|
||||
message_processor::MessageProcessor,
|
||||
sysvar_cache::SysvarCache,
|
||||
timings::{ExecuteDetailsTimings, ExecuteTimingType, ExecuteTimings},
|
||||
loaded_programs::{LoadedProgram, LoadedProgramType, LoadedPrograms},
|
||||
timings::{ExecuteTimingType, ExecuteTimings},
|
||||
},
|
||||
solana_sdk::{
|
||||
account::{
|
||||
create_account_shared_data_with_fields as create_account, create_executable_meta,
|
||||
from_account, Account, AccountSharedData, InheritableAccountFields, ReadableAccount,
|
||||
WritableAccount, PROGRAM_OWNERS,
|
||||
WritableAccount,
|
||||
},
|
||||
account_utils::StateMut,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
clock::{
|
||||
BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_HASHES_PER_TICK,
|
||||
DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE,
|
||||
|
@ -160,8 +144,6 @@ use {
|
|||
incinerator,
|
||||
inflation::Inflation,
|
||||
inner_instruction::InnerInstructions,
|
||||
instruction::InstructionError,
|
||||
loader_v4::{self, LoaderV4State, LoaderV4Status},
|
||||
message::{AccountKeys, SanitizedMessage},
|
||||
native_loader,
|
||||
native_token::LAMPORTS_PER_SOL,
|
||||
|
@ -183,9 +165,7 @@ use {
|
|||
self, MessageHash, Result, SanitizedTransaction, Transaction, TransactionError,
|
||||
TransactionVerificationMode, VersionedTransaction, MAX_TX_ACCOUNT_LOCKS,
|
||||
},
|
||||
transaction_context::{
|
||||
ExecutionRecord, TransactionAccount, TransactionContext, TransactionReturnData,
|
||||
},
|
||||
transaction_context::{TransactionAccount, TransactionReturnData},
|
||||
},
|
||||
solana_stake_program::stake_state::{
|
||||
self, InflationPointCalculationEvent, PointValue, StakeStateV2,
|
||||
|
@ -195,18 +175,16 @@ use {
|
|||
solana_vote_program::vote_state::VoteState,
|
||||
std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
fmt, mem,
|
||||
ops::{AddAssign, RangeInclusive},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::{
|
||||
atomic::{
|
||||
AtomicBool, AtomicI64, AtomicU64, AtomicUsize,
|
||||
Ordering::{self, AcqRel, Acquire, Relaxed},
|
||||
Ordering::{AcqRel, Acquire, Relaxed},
|
||||
},
|
||||
Arc, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
|
||||
},
|
||||
|
@ -315,14 +293,6 @@ impl BankRc {
|
|||
}
|
||||
}
|
||||
|
||||
enum ProgramAccountLoadResult {
|
||||
AccountNotFound,
|
||||
InvalidAccountData(ProgramRuntimeEnvironment),
|
||||
ProgramOfLoaderV1orV2(AccountSharedData),
|
||||
ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
|
||||
ProgramOfLoaderV4(AccountSharedData, Slot),
|
||||
}
|
||||
|
||||
pub struct LoadAndExecuteTransactionsOutput {
|
||||
pub loaded_transactions: Vec<TransactionLoadResult>,
|
||||
// Vector of results indicating whether a transaction was executed or could not
|
||||
|
@ -340,13 +310,6 @@ pub struct LoadAndExecuteTransactionsOutput {
|
|||
pub error_counters: TransactionErrorMetrics,
|
||||
}
|
||||
|
||||
pub struct LoadAndExecuteSanitizedTransactionsOutput {
|
||||
pub loaded_transactions: Vec<TransactionLoadResult>,
|
||||
// Vector of results indicating whether a transaction was executed or could not
|
||||
// be executed. Note executed transactions can still have failed!
|
||||
pub execution_results: Vec<TransactionExecutionResult>,
|
||||
}
|
||||
|
||||
pub struct TransactionSimulationResult {
|
||||
pub result: Result<()>,
|
||||
pub logs: TransactionLogMessages,
|
||||
|
@ -371,9 +334,6 @@ impl TransactionBalancesSet {
|
|||
}
|
||||
pub type TransactionBalances = Vec<Vec<u64>>;
|
||||
|
||||
/// A list of log messages emitted during a transaction
|
||||
pub type TransactionLogMessages = Vec<String>;
|
||||
|
||||
#[derive(Serialize, Deserialize, AbiExample, AbiEnumVisitor, Debug, PartialEq, Eq)]
|
||||
pub enum TransactionLogCollectorFilter {
|
||||
All,
|
||||
|
@ -773,7 +733,7 @@ pub struct Bank {
|
|||
rent_collector: RentCollector,
|
||||
|
||||
/// initialized from genesis
|
||||
epoch_schedule: EpochSchedule,
|
||||
pub(crate) epoch_schedule: EpochSchedule,
|
||||
|
||||
/// inflation specs
|
||||
inflation: Arc<RwLock<Inflation>>,
|
||||
|
@ -792,7 +752,7 @@ pub struct Bank {
|
|||
builtin_programs: HashSet<Pubkey>,
|
||||
|
||||
/// Optional config parameters that can override runtime behavior
|
||||
runtime_config: Arc<RuntimeConfig>,
|
||||
pub(crate) runtime_config: Arc<RuntimeConfig>,
|
||||
|
||||
/// Protocol-level rewards that were distributed by this bank
|
||||
pub rewards: RwLock<Vec<(Pubkey, RewardInfo)>>,
|
||||
|
@ -4590,555 +4550,14 @@ impl Bank {
|
|||
}
|
||||
balances
|
||||
}
|
||||
}
|
||||
|
||||
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 = callbacks
|
||||
.get_account_shared_data(&programdata_address)
|
||||
.ok_or(TransactionError::ProgramAccountNotFound)?;
|
||||
if let Ok(UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: _,
|
||||
}) = programdata.state()
|
||||
{
|
||||
return Ok(slot);
|
||||
}
|
||||
}
|
||||
Err(TransactionError::ProgramAccountNotFound)
|
||||
} else if loader_v4::check_id(program.owner()) {
|
||||
let state = solana_loader_v4_program::get_state(program.data())
|
||||
.map_err(|_| TransactionError::ProgramAccountNotFound)?;
|
||||
Ok(state.slot)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_program_accounts<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
environments: &ProgramRuntimeEnvironments,
|
||||
) -> ProgramAccountLoadResult {
|
||||
let program_account = match callbacks.get_account_shared_data(pubkey) {
|
||||
None => return ProgramAccountLoadResult::AccountNotFound,
|
||||
Some(account) => account,
|
||||
};
|
||||
|
||||
debug_assert!(solana_bpf_loader_program::check_loader_id(
|
||||
program_account.owner()
|
||||
));
|
||||
|
||||
if loader_v4::check_id(program_account.owner()) {
|
||||
return solana_loader_v4_program::get_state(program_account.data())
|
||||
.ok()
|
||||
.and_then(|state| {
|
||||
(!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot)
|
||||
})
|
||||
.map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
|
||||
.unwrap_or(ProgramAccountLoadResult::InvalidAccountData(
|
||||
environments.program_runtime_v2.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if !bpf_loader_upgradeable::check_id(program_account.owner()) {
|
||||
return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account);
|
||||
}
|
||||
|
||||
if let Ok(UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
}) = program_account.state()
|
||||
{
|
||||
let programdata_account = match callbacks.get_account_shared_data(&programdata_address)
|
||||
{
|
||||
None => return ProgramAccountLoadResult::AccountNotFound,
|
||||
Some(account) => account,
|
||||
};
|
||||
|
||||
if let Ok(UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: _,
|
||||
}) = programdata_account.state()
|
||||
{
|
||||
return ProgramAccountLoadResult::ProgramOfLoaderV3(
|
||||
program_account,
|
||||
programdata_account,
|
||||
slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
ProgramAccountLoadResult::InvalidAccountData(environments.program_runtime_v1.clone())
|
||||
}
|
||||
|
||||
fn load_program_from_bytes(
|
||||
load_program_metrics: &mut LoadProgramMetrics,
|
||||
programdata: &[u8],
|
||||
loader_key: &Pubkey,
|
||||
account_size: usize,
|
||||
deployment_slot: Slot,
|
||||
program_runtime_environment: ProgramRuntimeEnvironment,
|
||||
reloading: bool,
|
||||
) -> std::result::Result<LoadedProgram, Box<dyn std::error::Error>> {
|
||||
if reloading {
|
||||
// Safety: this is safe because the program is being reloaded in the cache.
|
||||
unsafe {
|
||||
LoadedProgram::reload(
|
||||
loader_key,
|
||||
program_runtime_environment.clone(),
|
||||
deployment_slot,
|
||||
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
|
||||
None,
|
||||
programdata,
|
||||
account_size,
|
||||
load_program_metrics,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LoadedProgram::new(
|
||||
loader_key,
|
||||
program_runtime_environment.clone(),
|
||||
deployment_slot,
|
||||
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
|
||||
None,
|
||||
programdata,
|
||||
account_size,
|
||||
load_program_metrics,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_program<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
reload: bool,
|
||||
recompile: Option<Arc<LoadedProgram>>,
|
||||
) -> Arc<LoadedProgram> {
|
||||
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
|
||||
let effective_epoch = if recompile.is_some() {
|
||||
loaded_programs_cache.latest_root_epoch.saturating_add(1)
|
||||
} else {
|
||||
self.epoch
|
||||
};
|
||||
let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch);
|
||||
let mut load_program_metrics = LoadProgramMetrics {
|
||||
program_id: pubkey.to_string(),
|
||||
..LoadProgramMetrics::default()
|
||||
};
|
||||
|
||||
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::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| {
|
||||
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.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);
|
||||
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<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callback: &CB,
|
||||
tx: &SanitizedTransaction,
|
||||
loaded_transaction: &mut LoadedTransaction,
|
||||
compute_budget: ComputeBudget,
|
||||
durable_nonce_fee: Option<DurableNonceFee>,
|
||||
enable_cpi_recording: bool,
|
||||
enable_log_recording: bool,
|
||||
enable_return_data_recording: bool,
|
||||
timings: &mut ExecuteTimings,
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
log_messages_bytes_limit: Option<usize>,
|
||||
programs_loaded_for_tx_batch: &LoadedProgramsForTxBatch,
|
||||
) -> TransactionExecutionResult {
|
||||
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
|
||||
|
||||
fn transaction_accounts_lamports_sum(
|
||||
accounts: &[(Pubkey, AccountSharedData)],
|
||||
message: &SanitizedMessage,
|
||||
) -> Option<u128> {
|
||||
let mut lamports_sum = 0u128;
|
||||
for i in 0..message.account_keys().len() {
|
||||
let (_, account) = accounts.get(i)?;
|
||||
lamports_sum = lamports_sum.checked_add(u128::from(account.lamports()))?;
|
||||
}
|
||||
Some(lamports_sum)
|
||||
}
|
||||
|
||||
let lamports_before_tx =
|
||||
transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0);
|
||||
|
||||
let mut transaction_context = TransactionContext::new(
|
||||
transaction_accounts,
|
||||
callback.get_rent_collector().rent.clone(),
|
||||
compute_budget.max_invoke_stack_height,
|
||||
compute_budget.max_instruction_trace_length,
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
transaction_context.set_signature(tx.signature());
|
||||
|
||||
let pre_account_state_info = TransactionAccountStateInfo::new(
|
||||
&callback.get_rent_collector().rent,
|
||||
&transaction_context,
|
||||
tx.message(),
|
||||
);
|
||||
|
||||
let log_collector = if enable_log_recording {
|
||||
match log_messages_bytes_limit {
|
||||
None => Some(LogCollector::new_ref()),
|
||||
Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
|
||||
log_messages_bytes_limit,
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
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(
|
||||
self.slot,
|
||||
programs_loaded_for_tx_batch.environments.clone(),
|
||||
);
|
||||
let mut process_message_time = Measure::start("process_message_time");
|
||||
let process_result = MessageProcessor::process_message(
|
||||
tx.message(),
|
||||
&loaded_transaction.program_indices,
|
||||
&mut transaction_context,
|
||||
log_collector.clone(),
|
||||
programs_loaded_for_tx_batch,
|
||||
&mut programs_modified_by_tx,
|
||||
callback.get_feature_set(),
|
||||
compute_budget,
|
||||
timings,
|
||||
&self.sysvar_cache.read().unwrap(),
|
||||
blockhash,
|
||||
lamports_per_signature,
|
||||
&mut executed_units,
|
||||
);
|
||||
process_message_time.stop();
|
||||
|
||||
saturating_add_assign!(
|
||||
timings.execute_accessories.process_message_us,
|
||||
process_message_time.as_us()
|
||||
);
|
||||
|
||||
let mut status = process_result
|
||||
.and_then(|info| {
|
||||
let post_account_state_info = TransactionAccountStateInfo::new(
|
||||
&callback.get_rent_collector().rent,
|
||||
&transaction_context,
|
||||
tx.message(),
|
||||
);
|
||||
TransactionAccountStateInfo::verify_changes(
|
||||
&pre_account_state_info,
|
||||
&post_account_state_info,
|
||||
&transaction_context,
|
||||
)
|
||||
.map(|_| info)
|
||||
})
|
||||
.map_err(|err| {
|
||||
match err {
|
||||
TransactionError::InvalidRentPayingAccount
|
||||
| TransactionError::InsufficientFundsForRent { .. } => {
|
||||
error_counters.invalid_rent_paying_account += 1;
|
||||
}
|
||||
TransactionError::InvalidAccountIndex => {
|
||||
error_counters.invalid_account_index += 1;
|
||||
}
|
||||
_ => {
|
||||
error_counters.instruction_error += 1;
|
||||
}
|
||||
}
|
||||
err
|
||||
});
|
||||
|
||||
let log_messages: Option<TransactionLogMessages> =
|
||||
log_collector.and_then(|log_collector| {
|
||||
Rc::try_unwrap(log_collector)
|
||||
.map(|log_collector| log_collector.into_inner().into_messages())
|
||||
.ok()
|
||||
});
|
||||
|
||||
let inner_instructions = if enable_cpi_recording {
|
||||
Some(inner_instructions_list_from_instruction_trace(
|
||||
&transaction_context,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ExecutionRecord {
|
||||
accounts,
|
||||
return_data,
|
||||
touched_account_count,
|
||||
accounts_resize_delta: accounts_data_len_delta,
|
||||
} = transaction_context.into();
|
||||
|
||||
if status.is_ok()
|
||||
&& transaction_accounts_lamports_sum(&accounts, tx.message())
|
||||
.filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
|
||||
.is_none()
|
||||
{
|
||||
status = Err(TransactionError::UnbalancedTransaction);
|
||||
}
|
||||
let status = status.map(|_| ());
|
||||
|
||||
loaded_transaction.accounts = accounts;
|
||||
saturating_add_assign!(
|
||||
timings.details.total_account_count,
|
||||
loaded_transaction.accounts.len() as u64
|
||||
);
|
||||
saturating_add_assign!(timings.details.changed_account_count, touched_account_count);
|
||||
|
||||
let return_data = if enable_return_data_recording && !return_data.data.is_empty() {
|
||||
Some(return_data)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
TransactionExecutionResult::Executed {
|
||||
details: TransactionExecutionDetails {
|
||||
status,
|
||||
log_messages,
|
||||
inner_instructions,
|
||||
durable_nonce_fee,
|
||||
return_data,
|
||||
executed_units,
|
||||
accounts_data_len_delta,
|
||||
},
|
||||
programs_modified_by_tx: Box::new(programs_modified_by_tx),
|
||||
}
|
||||
}
|
||||
|
||||
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))> =
|
||||
if self.check_program_modification_slot {
|
||||
program_accounts_map
|
||||
.iter()
|
||||
.map(|(pubkey, (_, count))| {
|
||||
(
|
||||
*pubkey,
|
||||
(
|
||||
self.program_modification_slot(callback, pubkey)
|
||||
.map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
|
||||
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
|
||||
}),
|
||||
*count,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
program_accounts_map
|
||||
.iter()
|
||||
.map(|(pubkey, (_, count))| {
|
||||
(*pubkey, (LoadedProgramMatchCriteria::NoCriteria, *count))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut loaded_programs_for_txs = None;
|
||||
let mut program_to_store = None;
|
||||
loop {
|
||||
let (program_to_load, task_cookie, task_waiter) = {
|
||||
// Lock the global cache.
|
||||
let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap();
|
||||
// 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(
|
||||
self.slot,
|
||||
loaded_programs_cache
|
||||
.get_environments_for_epoch(self.epoch)
|
||||
.clone(),
|
||||
));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
// Figure out which program needs to be loaded next.
|
||||
let program_to_load = loaded_programs_cache.extract(
|
||||
&mut missing_programs,
|
||||
loaded_programs_for_txs.as_mut().unwrap(),
|
||||
is_first_round,
|
||||
);
|
||||
let task_waiter = Arc::clone(&loaded_programs_cache.loading_task_waiter);
|
||||
(program_to_load, task_waiter.cookie(), task_waiter)
|
||||
// Unlock the global cache again.
|
||||
};
|
||||
|
||||
if let Some((key, count)) = program_to_load {
|
||||
// Load, verify and compile one program.
|
||||
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() {
|
||||
break;
|
||||
} else {
|
||||
// Sleep until the next finish_cooperative_loading_task() call.
|
||||
// Once a task completes we'll wake up and try to load the
|
||||
// missing programs inside the tx batch again.
|
||||
let _new_cookie = task_waiter.wait(task_cookie);
|
||||
}
|
||||
}
|
||||
|
||||
loaded_programs_for_txs.unwrap()
|
||||
}
|
||||
|
||||
/// 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, CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
txs: &[SanitizedTransaction],
|
||||
lock_results: &mut [TransactionCheckResult],
|
||||
program_owners: &'a [Pubkey],
|
||||
) -> HashMap<Pubkey, (&'a Pubkey, u64)> {
|
||||
let mut result: HashMap<Pubkey, (&'a Pubkey, u64)> = HashMap::new();
|
||||
lock_results.iter_mut().zip(txs).for_each(|etx| {
|
||||
if let ((Ok(()), _nonce, lamports_per_signature), tx) = etx {
|
||||
if lamports_per_signature.is_some() {
|
||||
tx.message()
|
||||
.account_keys()
|
||||
.iter()
|
||||
.for_each(|key| match result.entry(*key) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let (_, count) = entry.get_mut();
|
||||
saturating_add_assign!(*count, 1);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(index) =
|
||||
callbacks.account_matches_owners(key, program_owners)
|
||||
{
|
||||
program_owners
|
||||
.get(index)
|
||||
.map(|owner| entry.insert((owner, 1)));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the transaction's nonce account was not valid, and blockhash is not found,
|
||||
// the transaction will fail to process. Let's not load any programs from the
|
||||
// transaction, and update the status of the transaction.
|
||||
*etx.0 = (Err(TransactionError::BlockhashNotFound), None, None);
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn load_and_execute_transactions(
|
||||
&self,
|
||||
|
@ -5344,145 +4763,7 @@ impl Bank {
|
|||
error_counters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionBatchProcessor {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
sanitized_txs: &[SanitizedTransaction],
|
||||
check_results: &mut [TransactionCheckResult],
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
enable_cpi_recording: bool,
|
||||
enable_log_recording: bool,
|
||||
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(
|
||||
callbacks,
|
||||
sanitized_txs,
|
||||
check_results,
|
||||
PROGRAM_OWNERS,
|
||||
);
|
||||
let native_loader = native_loader::id();
|
||||
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(callbacks, &program_accounts_map),
|
||||
));
|
||||
|
||||
let mut load_time = Measure::start("accounts_load");
|
||||
let mut loaded_transactions = load_accounts(
|
||||
callbacks,
|
||||
sanitized_txs,
|
||||
check_results,
|
||||
error_counters,
|
||||
&self.fee_structure,
|
||||
account_overrides,
|
||||
&program_accounts_map,
|
||||
&programs_loaded_for_tx_batch.borrow(),
|
||||
);
|
||||
load_time.stop();
|
||||
|
||||
let mut execution_time = Measure::start("execution_time");
|
||||
|
||||
let execution_results: Vec<TransactionExecutionResult> = loaded_transactions
|
||||
.iter_mut()
|
||||
.zip(sanitized_txs.iter())
|
||||
.map(|(accs, tx)| match accs {
|
||||
(Err(e), _nonce) => TransactionExecutionResult::NotExecuted(e.clone()),
|
||||
(Ok(loaded_transaction), nonce) => {
|
||||
let compute_budget =
|
||||
if let Some(compute_budget) = self.runtime_config.compute_budget {
|
||||
compute_budget
|
||||
} else {
|
||||
let mut compute_budget_process_transaction_time =
|
||||
Measure::start("compute_budget_process_transaction_time");
|
||||
let maybe_compute_budget = ComputeBudget::try_from_instructions(
|
||||
tx.message().program_instructions_iter(),
|
||||
);
|
||||
compute_budget_process_transaction_time.stop();
|
||||
saturating_add_assign!(
|
||||
timings
|
||||
.execute_accessories
|
||||
.compute_budget_process_transaction_us,
|
||||
compute_budget_process_transaction_time.as_us()
|
||||
);
|
||||
if let Err(err) = maybe_compute_budget {
|
||||
return TransactionExecutionResult::NotExecuted(err);
|
||||
}
|
||||
maybe_compute_budget.unwrap()
|
||||
};
|
||||
|
||||
let result = self.execute_loaded_transaction(
|
||||
callbacks,
|
||||
tx,
|
||||
loaded_transaction,
|
||||
compute_budget,
|
||||
nonce.as_ref().map(DurableNonceFee::from),
|
||||
enable_cpi_recording,
|
||||
enable_log_recording,
|
||||
enable_return_data_recording,
|
||||
timings,
|
||||
error_counters,
|
||||
log_messages_bytes_limit,
|
||||
&programs_loaded_for_tx_batch.borrow(),
|
||||
);
|
||||
|
||||
if let TransactionExecutionResult::Executed {
|
||||
details,
|
||||
programs_modified_by_tx,
|
||||
} = &result
|
||||
{
|
||||
// Update batch specific cache of the loaded programs with the modifications
|
||||
// made by the transaction, if it executed successfully.
|
||||
if details.status.is_ok() {
|
||||
programs_loaded_for_tx_batch
|
||||
.borrow_mut()
|
||||
.merge(programs_modified_by_tx);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
execution_time.stop();
|
||||
|
||||
const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
|
||||
self.loaded_programs_cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.evict_using_2s_random_selection(
|
||||
Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
|
||||
self.slot,
|
||||
);
|
||||
|
||||
debug!(
|
||||
"load: {}us execute: {}us txs_len={}",
|
||||
load_time.as_us(),
|
||||
execution_time.as_us(),
|
||||
sanitized_txs.len(),
|
||||
);
|
||||
|
||||
timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_time.as_us());
|
||||
timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_time.as_us());
|
||||
|
||||
LoadAndExecuteSanitizedTransactionsOutput {
|
||||
loaded_transactions,
|
||||
execution_results,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
/// Load the accounts data size, in bytes
|
||||
pub fn load_accounts_data_size(&self) -> u64 {
|
||||
self.accounts_data_size_initial
|
||||
|
@ -8236,32 +7517,6 @@ impl Bank {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -8315,63 +7570,6 @@ impl TransactionProcessingCallback for Bank {
|
|||
}
|
||||
}
|
||||
|
||||
#[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")]
|
||||
impl Bank {
|
||||
pub fn wrap_with_bank_forks_for_tests(self) -> (Arc<Self>, Arc<RwLock<BankForks>>) {
|
||||
|
|
|
@ -18,6 +18,10 @@ use {
|
|||
},
|
||||
snapshot_bank_utils, snapshot_utils,
|
||||
status_cache::MAX_CACHE_ENTRIES,
|
||||
svm::{
|
||||
account_loader::load_accounts,
|
||||
transaction_account_state_info::TransactionAccountStateInfo,
|
||||
},
|
||||
},
|
||||
assert_matches::assert_matches,
|
||||
crossbeam_channel::{bounded, unbounded},
|
||||
|
@ -38,6 +42,7 @@ use {
|
|||
partitioned_rewards::TestPartitionedEpochRewards,
|
||||
rent_collector::RENT_EXEMPT_RENT_EPOCH,
|
||||
transaction_error_metrics::TransactionErrorMetrics,
|
||||
transaction_results::DurableNonceFee,
|
||||
},
|
||||
solana_logger,
|
||||
solana_program_runtime::{
|
||||
|
@ -45,7 +50,10 @@ use {
|
|||
compute_budget_processor::{self, MAX_COMPUTE_UNIT_LIMIT},
|
||||
declare_process_instruction,
|
||||
invoke_context::mock_process_instruction,
|
||||
loaded_programs::{LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET},
|
||||
loaded_programs::{
|
||||
LoadedProgram, LoadedProgramType, LoadedProgramsForTxBatch,
|
||||
DELAY_VISIBILITY_SLOT_OFFSET,
|
||||
},
|
||||
prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
|
||||
timings::ExecuteTimings,
|
||||
},
|
||||
|
@ -13741,8 +13749,7 @@ fn test_filter_executable_program_accounts() {
|
|||
let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2);
|
||||
|
||||
let owners = &[program1_pubkey, program2_pubkey];
|
||||
let transaction_processor = TransactionBatchProcessor::new(&bank);
|
||||
let programs = transaction_processor.filter_executable_program_accounts(
|
||||
let programs = TransactionBatchProcessor::filter_executable_program_accounts(
|
||||
&bank,
|
||||
&[sanitized_tx1, sanitized_tx2],
|
||||
&mut [(Ok(()), None, Some(0)), (Ok(()), None, Some(0))],
|
||||
|
@ -13837,8 +13844,7 @@ fn test_filter_executable_program_accounts_invalid_blockhash() {
|
|||
|
||||
let owners = &[program1_pubkey, program2_pubkey];
|
||||
let mut lock_results = vec![(Ok(()), None, Some(0)), (Ok(()), None, None)];
|
||||
let transaction_processor = TransactionBatchProcessor::new(&bank);
|
||||
let programs = transaction_processor.filter_executable_program_accounts(
|
||||
let programs = TransactionBatchProcessor::filter_executable_program_accounts(
|
||||
&bank,
|
||||
&[sanitized_tx1, sanitized_tx2],
|
||||
&mut lock_results,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use {
|
||||
crate::{bank::TransactionProcessingCallback, svm::account_rent_state::RentState},
|
||||
crate::svm::{
|
||||
account_rent_state::RentState, transaction_processor::TransactionProcessingCallback,
|
||||
},
|
||||
itertools::Itertools,
|
||||
log::warn,
|
||||
solana_accounts_db::{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod account_loader;
|
||||
pub mod account_rent_state;
|
||||
pub mod transaction_account_state_info;
|
||||
pub mod transaction_processor;
|
||||
|
|
|
@ -0,0 +1,831 @@
|
|||
use {
|
||||
crate::{
|
||||
bank::Bank,
|
||||
bank_forks::BankForks,
|
||||
runtime_config::RuntimeConfig,
|
||||
svm::{
|
||||
account_loader::load_accounts,
|
||||
transaction_account_state_info::TransactionAccountStateInfo,
|
||||
},
|
||||
},
|
||||
log::debug,
|
||||
percentage::Percentage,
|
||||
solana_accounts_db::{
|
||||
account_overrides::AccountOverrides,
|
||||
accounts::{LoadedTransaction, TransactionLoadResult},
|
||||
accounts_file::MatchAccountOwnerError,
|
||||
rent_collector::RentCollector,
|
||||
transaction_error_metrics::TransactionErrorMetrics,
|
||||
transaction_results::{
|
||||
inner_instructions_list_from_instruction_trace, DurableNonceFee,
|
||||
TransactionCheckResult, TransactionExecutionDetails, TransactionExecutionResult,
|
||||
},
|
||||
},
|
||||
solana_measure::measure::Measure,
|
||||
solana_program_runtime::{
|
||||
compute_budget::ComputeBudget,
|
||||
loaded_programs::{
|
||||
LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
|
||||
LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
|
||||
ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
|
||||
},
|
||||
log_collector::LogCollector,
|
||||
message_processor::MessageProcessor,
|
||||
sysvar_cache::SysvarCache,
|
||||
timings::{ExecuteDetailsTimings, ExecuteTimingType, ExecuteTimings},
|
||||
},
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount, PROGRAM_OWNERS},
|
||||
account_utils::StateMut,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
clock::{Epoch, Slot},
|
||||
epoch_schedule::EpochSchedule,
|
||||
feature_set::FeatureSet,
|
||||
fee::FeeStructure,
|
||||
hash::Hash,
|
||||
instruction::InstructionError,
|
||||
loader_v4::{self, LoaderV4State, LoaderV4Status},
|
||||
message::SanitizedMessage,
|
||||
native_loader,
|
||||
pubkey::Pubkey,
|
||||
saturating_add_assign,
|
||||
transaction::{self, SanitizedTransaction, TransactionError},
|
||||
transaction_context::{ExecutionRecord, TransactionContext},
|
||||
},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// A list of log messages emitted during a transaction
|
||||
pub type TransactionLogMessages = Vec<String>;
|
||||
|
||||
pub struct LoadAndExecuteSanitizedTransactionsOutput {
|
||||
pub loaded_transactions: Vec<TransactionLoadResult>,
|
||||
// Vector of results indicating whether a transaction was executed or could not
|
||||
// be executed. Note executed transactions can still have failed!
|
||||
pub execution_results: Vec<TransactionExecutionResult>,
|
||||
}
|
||||
|
||||
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,
|
||||
) -> transaction::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum ProgramAccountLoadResult {
|
||||
AccountNotFound,
|
||||
InvalidAccountData(ProgramRuntimeEnvironment),
|
||||
ProgramOfLoaderV1orV2(AccountSharedData),
|
||||
ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
|
||||
ProgramOfLoaderV4(AccountSharedData, Slot),
|
||||
}
|
||||
|
||||
#[derive(AbiExample, Debug)]
|
||||
pub 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>,
|
||||
|
||||
pub 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 {
|
||||
pub 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(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
sanitized_txs: &[SanitizedTransaction],
|
||||
check_results: &mut [TransactionCheckResult],
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
enable_cpi_recording: bool,
|
||||
enable_log_recording: bool,
|
||||
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(
|
||||
callbacks,
|
||||
sanitized_txs,
|
||||
check_results,
|
||||
PROGRAM_OWNERS,
|
||||
);
|
||||
let native_loader = native_loader::id();
|
||||
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(callbacks, &program_accounts_map),
|
||||
));
|
||||
|
||||
let mut load_time = Measure::start("accounts_load");
|
||||
let mut loaded_transactions = load_accounts(
|
||||
callbacks,
|
||||
sanitized_txs,
|
||||
check_results,
|
||||
error_counters,
|
||||
&self.fee_structure,
|
||||
account_overrides,
|
||||
&program_accounts_map,
|
||||
&programs_loaded_for_tx_batch.borrow(),
|
||||
);
|
||||
load_time.stop();
|
||||
|
||||
let mut execution_time = Measure::start("execution_time");
|
||||
|
||||
let execution_results: Vec<TransactionExecutionResult> = loaded_transactions
|
||||
.iter_mut()
|
||||
.zip(sanitized_txs.iter())
|
||||
.map(|(accs, tx)| match accs {
|
||||
(Err(e), _nonce) => TransactionExecutionResult::NotExecuted(e.clone()),
|
||||
(Ok(loaded_transaction), nonce) => {
|
||||
let compute_budget =
|
||||
if let Some(compute_budget) = self.runtime_config.compute_budget {
|
||||
compute_budget
|
||||
} else {
|
||||
let mut compute_budget_process_transaction_time =
|
||||
Measure::start("compute_budget_process_transaction_time");
|
||||
let maybe_compute_budget = ComputeBudget::try_from_instructions(
|
||||
tx.message().program_instructions_iter(),
|
||||
);
|
||||
compute_budget_process_transaction_time.stop();
|
||||
saturating_add_assign!(
|
||||
timings
|
||||
.execute_accessories
|
||||
.compute_budget_process_transaction_us,
|
||||
compute_budget_process_transaction_time.as_us()
|
||||
);
|
||||
if let Err(err) = maybe_compute_budget {
|
||||
return TransactionExecutionResult::NotExecuted(err);
|
||||
}
|
||||
maybe_compute_budget.unwrap()
|
||||
};
|
||||
|
||||
let result = self.execute_loaded_transaction(
|
||||
callbacks,
|
||||
tx,
|
||||
loaded_transaction,
|
||||
compute_budget,
|
||||
nonce.as_ref().map(DurableNonceFee::from),
|
||||
enable_cpi_recording,
|
||||
enable_log_recording,
|
||||
enable_return_data_recording,
|
||||
timings,
|
||||
error_counters,
|
||||
log_messages_bytes_limit,
|
||||
&programs_loaded_for_tx_batch.borrow(),
|
||||
);
|
||||
|
||||
if let TransactionExecutionResult::Executed {
|
||||
details,
|
||||
programs_modified_by_tx,
|
||||
} = &result
|
||||
{
|
||||
// Update batch specific cache of the loaded programs with the modifications
|
||||
// made by the transaction, if it executed successfully.
|
||||
if details.status.is_ok() {
|
||||
programs_loaded_for_tx_batch
|
||||
.borrow_mut()
|
||||
.merge(programs_modified_by_tx);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
execution_time.stop();
|
||||
|
||||
const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
|
||||
self.loaded_programs_cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.evict_using_2s_random_selection(
|
||||
Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
|
||||
self.slot,
|
||||
);
|
||||
|
||||
debug!(
|
||||
"load: {}us execute: {}us txs_len={}",
|
||||
load_time.as_us(),
|
||||
execution_time.as_us(),
|
||||
sanitized_txs.len(),
|
||||
);
|
||||
|
||||
timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_time.as_us());
|
||||
timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_time.as_us());
|
||||
|
||||
LoadAndExecuteSanitizedTransactionsOutput {
|
||||
loaded_transactions,
|
||||
execution_results,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn filter_executable_program_accounts<'a, CB: TransactionProcessingCallback>(
|
||||
callbacks: &CB,
|
||||
txs: &[SanitizedTransaction],
|
||||
lock_results: &mut [TransactionCheckResult],
|
||||
program_owners: &'a [Pubkey],
|
||||
) -> HashMap<Pubkey, (&'a Pubkey, u64)> {
|
||||
let mut result: HashMap<Pubkey, (&'a Pubkey, u64)> = HashMap::new();
|
||||
lock_results.iter_mut().zip(txs).for_each(|etx| {
|
||||
if let ((Ok(()), _nonce, lamports_per_signature), tx) = etx {
|
||||
if lamports_per_signature.is_some() {
|
||||
tx.message()
|
||||
.account_keys()
|
||||
.iter()
|
||||
.for_each(|key| match result.entry(*key) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let (_, count) = entry.get_mut();
|
||||
saturating_add_assign!(*count, 1);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(index) =
|
||||
callbacks.account_matches_owners(key, program_owners)
|
||||
{
|
||||
program_owners
|
||||
.get(index)
|
||||
.map(|owner| entry.insert((owner, 1)));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the transaction's nonce account was not valid, and blockhash is not found,
|
||||
// the transaction will fail to process. Let's not load any programs from the
|
||||
// transaction, and update the status of the transaction.
|
||||
*etx.0 = (Err(TransactionError::BlockhashNotFound), None, None);
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
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))> =
|
||||
if self.check_program_modification_slot {
|
||||
program_accounts_map
|
||||
.iter()
|
||||
.map(|(pubkey, (_, count))| {
|
||||
(
|
||||
*pubkey,
|
||||
(
|
||||
self.program_modification_slot(callback, pubkey)
|
||||
.map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
|
||||
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
|
||||
}),
|
||||
*count,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
program_accounts_map
|
||||
.iter()
|
||||
.map(|(pubkey, (_, count))| {
|
||||
(*pubkey, (LoadedProgramMatchCriteria::NoCriteria, *count))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut loaded_programs_for_txs = None;
|
||||
let mut program_to_store = None;
|
||||
loop {
|
||||
let (program_to_load, task_cookie, task_waiter) = {
|
||||
// Lock the global cache.
|
||||
let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap();
|
||||
// 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(
|
||||
self.slot,
|
||||
loaded_programs_cache
|
||||
.get_environments_for_epoch(self.epoch)
|
||||
.clone(),
|
||||
));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
// Figure out which program needs to be loaded next.
|
||||
let program_to_load = loaded_programs_cache.extract(
|
||||
&mut missing_programs,
|
||||
loaded_programs_for_txs.as_mut().unwrap(),
|
||||
is_first_round,
|
||||
);
|
||||
let task_waiter = Arc::clone(&loaded_programs_cache.loading_task_waiter);
|
||||
(program_to_load, task_waiter.cookie(), task_waiter)
|
||||
// Unlock the global cache again.
|
||||
};
|
||||
|
||||
if let Some((key, count)) = program_to_load {
|
||||
// Load, verify and compile one program.
|
||||
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() {
|
||||
break;
|
||||
} else {
|
||||
// Sleep until the next finish_cooperative_loading_task() call.
|
||||
// Once a task completes we'll wake up and try to load the
|
||||
// missing programs inside the tx batch again.
|
||||
let _new_cookie = task_waiter.wait(task_cookie);
|
||||
}
|
||||
}
|
||||
|
||||
loaded_programs_for_txs.unwrap()
|
||||
}
|
||||
|
||||
/// 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<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callback: &CB,
|
||||
tx: &SanitizedTransaction,
|
||||
loaded_transaction: &mut LoadedTransaction,
|
||||
compute_budget: ComputeBudget,
|
||||
durable_nonce_fee: Option<DurableNonceFee>,
|
||||
enable_cpi_recording: bool,
|
||||
enable_log_recording: bool,
|
||||
enable_return_data_recording: bool,
|
||||
timings: &mut ExecuteTimings,
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
log_messages_bytes_limit: Option<usize>,
|
||||
programs_loaded_for_tx_batch: &LoadedProgramsForTxBatch,
|
||||
) -> TransactionExecutionResult {
|
||||
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
|
||||
|
||||
fn transaction_accounts_lamports_sum(
|
||||
accounts: &[(Pubkey, AccountSharedData)],
|
||||
message: &SanitizedMessage,
|
||||
) -> Option<u128> {
|
||||
let mut lamports_sum = 0u128;
|
||||
for i in 0..message.account_keys().len() {
|
||||
let (_, account) = accounts.get(i)?;
|
||||
lamports_sum = lamports_sum.checked_add(u128::from(account.lamports()))?;
|
||||
}
|
||||
Some(lamports_sum)
|
||||
}
|
||||
|
||||
let lamports_before_tx =
|
||||
transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0);
|
||||
|
||||
let mut transaction_context = TransactionContext::new(
|
||||
transaction_accounts,
|
||||
callback.get_rent_collector().rent.clone(),
|
||||
compute_budget.max_invoke_stack_height,
|
||||
compute_budget.max_instruction_trace_length,
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
transaction_context.set_signature(tx.signature());
|
||||
|
||||
let pre_account_state_info = TransactionAccountStateInfo::new(
|
||||
&callback.get_rent_collector().rent,
|
||||
&transaction_context,
|
||||
tx.message(),
|
||||
);
|
||||
|
||||
let log_collector = if enable_log_recording {
|
||||
match log_messages_bytes_limit {
|
||||
None => Some(LogCollector::new_ref()),
|
||||
Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
|
||||
log_messages_bytes_limit,
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
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(
|
||||
self.slot,
|
||||
programs_loaded_for_tx_batch.environments.clone(),
|
||||
);
|
||||
let mut process_message_time = Measure::start("process_message_time");
|
||||
let process_result = MessageProcessor::process_message(
|
||||
tx.message(),
|
||||
&loaded_transaction.program_indices,
|
||||
&mut transaction_context,
|
||||
log_collector.clone(),
|
||||
programs_loaded_for_tx_batch,
|
||||
&mut programs_modified_by_tx,
|
||||
callback.get_feature_set(),
|
||||
compute_budget,
|
||||
timings,
|
||||
&self.sysvar_cache.read().unwrap(),
|
||||
blockhash,
|
||||
lamports_per_signature,
|
||||
&mut executed_units,
|
||||
);
|
||||
process_message_time.stop();
|
||||
|
||||
saturating_add_assign!(
|
||||
timings.execute_accessories.process_message_us,
|
||||
process_message_time.as_us()
|
||||
);
|
||||
|
||||
let mut status = process_result
|
||||
.and_then(|info| {
|
||||
let post_account_state_info = TransactionAccountStateInfo::new(
|
||||
&callback.get_rent_collector().rent,
|
||||
&transaction_context,
|
||||
tx.message(),
|
||||
);
|
||||
TransactionAccountStateInfo::verify_changes(
|
||||
&pre_account_state_info,
|
||||
&post_account_state_info,
|
||||
&transaction_context,
|
||||
)
|
||||
.map(|_| info)
|
||||
})
|
||||
.map_err(|err| {
|
||||
match err {
|
||||
TransactionError::InvalidRentPayingAccount
|
||||
| TransactionError::InsufficientFundsForRent { .. } => {
|
||||
error_counters.invalid_rent_paying_account += 1;
|
||||
}
|
||||
TransactionError::InvalidAccountIndex => {
|
||||
error_counters.invalid_account_index += 1;
|
||||
}
|
||||
_ => {
|
||||
error_counters.instruction_error += 1;
|
||||
}
|
||||
}
|
||||
err
|
||||
});
|
||||
|
||||
let log_messages: Option<TransactionLogMessages> =
|
||||
log_collector.and_then(|log_collector| {
|
||||
Rc::try_unwrap(log_collector)
|
||||
.map(|log_collector| log_collector.into_inner().into_messages())
|
||||
.ok()
|
||||
});
|
||||
|
||||
let inner_instructions = if enable_cpi_recording {
|
||||
Some(inner_instructions_list_from_instruction_trace(
|
||||
&transaction_context,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ExecutionRecord {
|
||||
accounts,
|
||||
return_data,
|
||||
touched_account_count,
|
||||
accounts_resize_delta: accounts_data_len_delta,
|
||||
} = transaction_context.into();
|
||||
|
||||
if status.is_ok()
|
||||
&& transaction_accounts_lamports_sum(&accounts, tx.message())
|
||||
.filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
|
||||
.is_none()
|
||||
{
|
||||
status = Err(TransactionError::UnbalancedTransaction);
|
||||
}
|
||||
let status = status.map(|_| ());
|
||||
|
||||
loaded_transaction.accounts = accounts;
|
||||
saturating_add_assign!(
|
||||
timings.details.total_account_count,
|
||||
loaded_transaction.accounts.len() as u64
|
||||
);
|
||||
saturating_add_assign!(timings.details.changed_account_count, touched_account_count);
|
||||
|
||||
let return_data = if enable_return_data_recording && !return_data.data.is_empty() {
|
||||
Some(return_data)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
TransactionExecutionResult::Executed {
|
||||
details: TransactionExecutionDetails {
|
||||
status,
|
||||
log_messages,
|
||||
inner_instructions,
|
||||
durable_nonce_fee,
|
||||
return_data,
|
||||
executed_units,
|
||||
accounts_data_len_delta,
|
||||
},
|
||||
programs_modified_by_tx: Box::new(programs_modified_by_tx),
|
||||
}
|
||||
}
|
||||
|
||||
fn program_modification_slot<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
) -> transaction::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 = callbacks
|
||||
.get_account_shared_data(&programdata_address)
|
||||
.ok_or(TransactionError::ProgramAccountNotFound)?;
|
||||
if let Ok(UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: _,
|
||||
}) = programdata.state()
|
||||
{
|
||||
return Ok(slot);
|
||||
}
|
||||
}
|
||||
Err(TransactionError::ProgramAccountNotFound)
|
||||
} else if loader_v4::check_id(program.owner()) {
|
||||
let state = solana_loader_v4_program::get_state(program.data())
|
||||
.map_err(|_| TransactionError::ProgramAccountNotFound)?;
|
||||
Ok(state.slot)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_program<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
reload: bool,
|
||||
recompile: Option<Arc<LoadedProgram>>,
|
||||
) -> Arc<LoadedProgram> {
|
||||
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
|
||||
let effective_epoch = if recompile.is_some() {
|
||||
loaded_programs_cache.latest_root_epoch.saturating_add(1)
|
||||
} else {
|
||||
self.epoch
|
||||
};
|
||||
let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch);
|
||||
let mut load_program_metrics = LoadProgramMetrics {
|
||||
program_id: pubkey.to_string(),
|
||||
..LoadProgramMetrics::default()
|
||||
};
|
||||
|
||||
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::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| {
|
||||
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.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);
|
||||
Arc::new(loaded_program)
|
||||
}
|
||||
|
||||
fn load_program_from_bytes(
|
||||
load_program_metrics: &mut LoadProgramMetrics,
|
||||
programdata: &[u8],
|
||||
loader_key: &Pubkey,
|
||||
account_size: usize,
|
||||
deployment_slot: Slot,
|
||||
program_runtime_environment: ProgramRuntimeEnvironment,
|
||||
reloading: bool,
|
||||
) -> std::result::Result<LoadedProgram, Box<dyn std::error::Error>> {
|
||||
if reloading {
|
||||
// Safety: this is safe because the program is being reloaded in the cache.
|
||||
unsafe {
|
||||
LoadedProgram::reload(
|
||||
loader_key,
|
||||
program_runtime_environment.clone(),
|
||||
deployment_slot,
|
||||
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
|
||||
None,
|
||||
programdata,
|
||||
account_size,
|
||||
load_program_metrics,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LoadedProgram::new(
|
||||
loader_key,
|
||||
program_runtime_environment.clone(),
|
||||
deployment_slot,
|
||||
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
|
||||
None,
|
||||
programdata,
|
||||
account_size,
|
||||
load_program_metrics,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_program_accounts<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
environments: &ProgramRuntimeEnvironments,
|
||||
) -> ProgramAccountLoadResult {
|
||||
let program_account = match callbacks.get_account_shared_data(pubkey) {
|
||||
None => return ProgramAccountLoadResult::AccountNotFound,
|
||||
Some(account) => account,
|
||||
};
|
||||
|
||||
debug_assert!(solana_bpf_loader_program::check_loader_id(
|
||||
program_account.owner()
|
||||
));
|
||||
|
||||
if loader_v4::check_id(program_account.owner()) {
|
||||
return solana_loader_v4_program::get_state(program_account.data())
|
||||
.ok()
|
||||
.and_then(|state| {
|
||||
(!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot)
|
||||
})
|
||||
.map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
|
||||
.unwrap_or(ProgramAccountLoadResult::InvalidAccountData(
|
||||
environments.program_runtime_v2.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if !bpf_loader_upgradeable::check_id(program_account.owner()) {
|
||||
return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account);
|
||||
}
|
||||
|
||||
if let Ok(UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
}) = program_account.state()
|
||||
{
|
||||
let programdata_account = match callbacks.get_account_shared_data(&programdata_address)
|
||||
{
|
||||
None => return ProgramAccountLoadResult::AccountNotFound,
|
||||
Some(account) => account,
|
||||
};
|
||||
|
||||
if let Ok(UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: _,
|
||||
}) = programdata_account.state()
|
||||
{
|
||||
return ProgramAccountLoadResult::ProgramOfLoaderV3(
|
||||
program_account,
|
||||
programdata_account,
|
||||
slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
ProgramAccountLoadResult::InvalidAccountData(environments.program_runtime_v1.clone())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue