SVM: Add doc comments, restrict visibility of some xfaces to crate (#136)
This commit is contained in:
parent
bf0a3684eb
commit
d88050cda3
|
@ -7499,7 +7499,7 @@ impl Bank {
|
|||
effective_epoch: Epoch,
|
||||
) -> Arc<LoadedProgram> {
|
||||
self.transaction_processor
|
||||
.load_program(self, pubkey, reload, effective_epoch)
|
||||
.load_program_with_pubkey(self, pubkey, reload, effective_epoch)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,11 @@ use {
|
|||
};
|
||||
|
||||
// for the load instructions
|
||||
pub type TransactionRent = u64;
|
||||
pub type TransactionProgramIndices = Vec<Vec<IndexOfAccount>>;
|
||||
pub(crate) type TransactionRent = u64;
|
||||
pub(crate) type TransactionProgramIndices = Vec<Vec<IndexOfAccount>>;
|
||||
pub type TransactionCheckResult = (transaction::Result<()>, Option<NoncePartial>, Option<u64>);
|
||||
pub type TransactionLoadResult = (Result<LoadedTransaction>, Option<NonceFull>);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct LoadedTransaction {
|
||||
pub accounts: Vec<TransactionAccount>,
|
||||
|
@ -48,10 +51,66 @@ pub struct LoadedTransaction {
|
|||
pub rent_debits: RentDebits,
|
||||
}
|
||||
|
||||
pub type TransactionLoadResult = (Result<LoadedTransaction>, Option<NonceFull>);
|
||||
pub type TransactionCheckResult = (transaction::Result<()>, Option<NoncePartial>, Option<u64>);
|
||||
/// Check whether the payer_account is capable of paying the fee. The
|
||||
/// side effect is to subtract the fee amount from the payer_account
|
||||
/// balance of lamports. If the payer_acount is not able to pay the
|
||||
/// fee, the error_counters is incremented, and a specific error is
|
||||
/// returned.
|
||||
pub fn validate_fee_payer(
|
||||
payer_address: &Pubkey,
|
||||
payer_account: &mut AccountSharedData,
|
||||
payer_index: IndexOfAccount,
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
rent_collector: &RentCollector,
|
||||
fee: u64,
|
||||
) -> Result<()> {
|
||||
if payer_account.lamports() == 0 {
|
||||
error_counters.account_not_found += 1;
|
||||
return Err(TransactionError::AccountNotFound);
|
||||
}
|
||||
let system_account_kind = get_system_account_kind(payer_account).ok_or_else(|| {
|
||||
error_counters.invalid_account_for_fee += 1;
|
||||
TransactionError::InvalidAccountForFee
|
||||
})?;
|
||||
let min_balance = match system_account_kind {
|
||||
SystemAccountKind::System => 0,
|
||||
SystemAccountKind::Nonce => {
|
||||
// Should we ever allow a fees charge to zero a nonce account's
|
||||
// balance. The state MUST be set to uninitialized in that case
|
||||
rent_collector.rent.minimum_balance(NonceState::size())
|
||||
}
|
||||
};
|
||||
|
||||
pub fn load_accounts<CB: TransactionProcessingCallback>(
|
||||
payer_account
|
||||
.lamports()
|
||||
.checked_sub(min_balance)
|
||||
.and_then(|v| v.checked_sub(fee))
|
||||
.ok_or_else(|| {
|
||||
error_counters.insufficient_funds += 1;
|
||||
TransactionError::InsufficientFundsForFee
|
||||
})?;
|
||||
|
||||
let payer_pre_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
|
||||
payer_account
|
||||
.checked_sub_lamports(fee)
|
||||
.map_err(|_| TransactionError::InsufficientFundsForFee)?;
|
||||
|
||||
let payer_post_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
|
||||
RentState::check_rent_state_with_account(
|
||||
&payer_pre_rent_state,
|
||||
&payer_post_rent_state,
|
||||
payer_address,
|
||||
payer_account,
|
||||
payer_index,
|
||||
)
|
||||
}
|
||||
|
||||
/// Collect information about accounts used in txs transactions and
|
||||
/// return vector of tuples, one for each transaction in the
|
||||
/// batch. Each tuple contains struct of information about accounts as
|
||||
/// its first element and an optional transaction nonce info as its
|
||||
/// second element.
|
||||
pub(crate) fn load_accounts<CB: TransactionProcessingCallback>(
|
||||
callbacks: &CB,
|
||||
txs: &[SanitizedTransaction],
|
||||
lock_results: &[TransactionCheckResult],
|
||||
|
@ -399,55 +458,6 @@ fn accumulate_and_check_loaded_account_data_size(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn validate_fee_payer(
|
||||
payer_address: &Pubkey,
|
||||
payer_account: &mut AccountSharedData,
|
||||
payer_index: IndexOfAccount,
|
||||
error_counters: &mut TransactionErrorMetrics,
|
||||
rent_collector: &RentCollector,
|
||||
fee: u64,
|
||||
) -> Result<()> {
|
||||
if payer_account.lamports() == 0 {
|
||||
error_counters.account_not_found += 1;
|
||||
return Err(TransactionError::AccountNotFound);
|
||||
}
|
||||
let system_account_kind = get_system_account_kind(payer_account).ok_or_else(|| {
|
||||
error_counters.invalid_account_for_fee += 1;
|
||||
TransactionError::InvalidAccountForFee
|
||||
})?;
|
||||
let min_balance = match system_account_kind {
|
||||
SystemAccountKind::System => 0,
|
||||
SystemAccountKind::Nonce => {
|
||||
// Should we ever allow a fees charge to zero a nonce account's
|
||||
// balance. The state MUST be set to uninitialized in that case
|
||||
rent_collector.rent.minimum_balance(NonceState::size())
|
||||
}
|
||||
};
|
||||
|
||||
payer_account
|
||||
.lamports()
|
||||
.checked_sub(min_balance)
|
||||
.and_then(|v| v.checked_sub(fee))
|
||||
.ok_or_else(|| {
|
||||
error_counters.insufficient_funds += 1;
|
||||
TransactionError::InsufficientFundsForFee
|
||||
})?;
|
||||
|
||||
let payer_pre_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
|
||||
payer_account
|
||||
.checked_sub_lamports(fee)
|
||||
.map_err(|_| TransactionError::InsufficientFundsForFee)?;
|
||||
|
||||
let payer_post_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
|
||||
RentState::check_rent_state_with_account(
|
||||
&payer_pre_rent_state,
|
||||
&payer_post_rent_state,
|
||||
payer_address,
|
||||
payer_account,
|
||||
payer_index,
|
||||
)
|
||||
}
|
||||
|
||||
fn construct_instructions_account(message: &SanitizedMessage) -> AccountSharedData {
|
||||
AccountSharedData::from(Account {
|
||||
data: construct_instructions_data(&message.decompile_instructions()),
|
||||
|
@ -460,11 +470,15 @@ fn construct_instructions_account(message: &SanitizedMessage) -> AccountSharedDa
|
|||
mod tests {
|
||||
use {
|
||||
super::*,
|
||||
crate::transaction_processor::TransactionProcessingCallback,
|
||||
crate::{
|
||||
transaction_account_state_info::TransactionAccountStateInfo,
|
||||
transaction_processor::TransactionProcessingCallback,
|
||||
},
|
||||
nonce::state::Versions as NonceVersions,
|
||||
solana_program_runtime::{
|
||||
compute_budget::ComputeBudget,
|
||||
compute_budget_processor,
|
||||
loaded_programs::LoadedProgram,
|
||||
loaded_programs::{LoadedProgram, LoadedProgramsForTxBatch},
|
||||
prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
|
||||
},
|
||||
solana_sdk::{
|
||||
|
@ -473,22 +487,27 @@ mod tests {
|
|||
compute_budget::ComputeBudgetInstruction,
|
||||
epoch_schedule::EpochSchedule,
|
||||
feature_set::FeatureSet,
|
||||
fee::FeeStructure,
|
||||
hash::Hash,
|
||||
instruction::CompiledInstruction,
|
||||
message::{
|
||||
v0::{LoadedAddresses, LoadedMessage},
|
||||
LegacyMessage, Message, MessageHeader, SanitizedMessage,
|
||||
},
|
||||
native_loader,
|
||||
native_token::sol_to_lamports,
|
||||
nonce,
|
||||
nonce_info::{NonceFull, NoncePartial},
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
rent_collector::RentCollector,
|
||||
rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH},
|
||||
rent_debits::RentDebits,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
system_program, sysvar,
|
||||
transaction::{Result, Transaction, TransactionError},
|
||||
transaction_context::TransactionAccount,
|
||||
system_program, system_transaction, sysvar,
|
||||
transaction::{Result, SanitizedTransaction, Transaction, TransactionError},
|
||||
transaction_context::{TransactionAccount, TransactionContext},
|
||||
},
|
||||
std::{borrow::Cow, convert::TryFrom, sync::Arc},
|
||||
std::{borrow::Cow, collections::HashMap, convert::TryFrom, sync::Arc},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -2017,4 +2036,248 @@ mod tests {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_state_list_len() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mut bank = TestCallbacks::default();
|
||||
let recipient = Pubkey::new_unique();
|
||||
let last_block_hash = Hash::new_unique();
|
||||
|
||||
let mut system_data = AccountSharedData::default();
|
||||
system_data.set_executable(true);
|
||||
system_data.set_owner(native_loader::id());
|
||||
bank.accounts_map
|
||||
.insert(Pubkey::new_from_array([0u8; 32]), system_data);
|
||||
|
||||
let mut mint_data = AccountSharedData::default();
|
||||
mint_data.set_lamports(2);
|
||||
bank.accounts_map.insert(mint_keypair.pubkey(), mint_data);
|
||||
|
||||
bank.accounts_map
|
||||
.insert(recipient, AccountSharedData::default());
|
||||
|
||||
let tx = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&recipient,
|
||||
sol_to_lamports(1.),
|
||||
last_block_hash,
|
||||
);
|
||||
let num_accounts = tx.message().account_keys.len();
|
||||
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx);
|
||||
let mut error_counters = TransactionErrorMetrics::default();
|
||||
let loaded_txs = load_accounts(
|
||||
&bank,
|
||||
&[sanitized_tx.clone()],
|
||||
&[(Ok(()), None, Some(0))],
|
||||
&mut error_counters,
|
||||
&FeeStructure::default(),
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
let compute_budget = ComputeBudget::new(u64::from(
|
||||
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
|
||||
));
|
||||
let transaction_context = TransactionContext::new(
|
||||
loaded_txs[0].0.as_ref().unwrap().accounts.clone(),
|
||||
Rent::default(),
|
||||
compute_budget.max_invoke_stack_height,
|
||||
compute_budget.max_instruction_trace_length,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TransactionAccountStateInfo::new(
|
||||
&Rent::default(),
|
||||
&transaction_context,
|
||||
sanitized_tx.message()
|
||||
)
|
||||
.len(),
|
||||
num_accounts,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_success() {
|
||||
let key1 = Keypair::new();
|
||||
let key2 = Keypair::new();
|
||||
let key3 = Keypair::new();
|
||||
let key4 = Keypair::new();
|
||||
|
||||
let message = Message {
|
||||
account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()],
|
||||
header: MessageHeader::default(),
|
||||
instructions: vec![
|
||||
CompiledInstruction {
|
||||
program_id_index: 1,
|
||||
accounts: vec![0],
|
||||
data: vec![],
|
||||
},
|
||||
CompiledInstruction {
|
||||
program_id_index: 1,
|
||||
accounts: vec![2],
|
||||
data: vec![],
|
||||
},
|
||||
],
|
||||
recent_blockhash: Hash::default(),
|
||||
};
|
||||
|
||||
let legacy = LegacyMessage::new(message);
|
||||
let sanitized_message = SanitizedMessage::Legacy(legacy);
|
||||
let mut mock_bank = TestCallbacks::default();
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_executable(true);
|
||||
account_data.set_owner(key3.pubkey());
|
||||
mock_bank.accounts_map.insert(key1.pubkey(), account_data);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_lamports(200);
|
||||
mock_bank.accounts_map.insert(key2.pubkey(), account_data);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_executable(true);
|
||||
account_data.set_owner(native_loader::id());
|
||||
mock_bank.accounts_map.insert(key3.pubkey(), account_data);
|
||||
|
||||
let mut error_counter = TransactionErrorMetrics::default();
|
||||
let loaded_programs = LoadedProgramsForTxBatch::default();
|
||||
|
||||
let sanitized_transaction = SanitizedTransaction::new_for_tests(
|
||||
sanitized_message,
|
||||
vec![Signature::new_unique()],
|
||||
false,
|
||||
);
|
||||
let lock_results =
|
||||
(Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult;
|
||||
|
||||
let results = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction],
|
||||
&[lock_results],
|
||||
&mut error_counter,
|
||||
&FeeStructure::default(),
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&loaded_programs,
|
||||
);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
let (loaded_result, nonce) = results[0].clone();
|
||||
assert_eq!(
|
||||
loaded_result.unwrap(),
|
||||
LoadedTransaction {
|
||||
accounts: vec![
|
||||
(
|
||||
key2.pubkey(),
|
||||
mock_bank.accounts_map[&key2.pubkey()].clone()
|
||||
),
|
||||
(
|
||||
key1.pubkey(),
|
||||
mock_bank.accounts_map[&key1.pubkey()].clone()
|
||||
),
|
||||
(key4.pubkey(), account_data),
|
||||
(
|
||||
key3.pubkey(),
|
||||
mock_bank.accounts_map[&key3.pubkey()].clone()
|
||||
),
|
||||
],
|
||||
program_indices: vec![vec![3, 1], vec![3, 1]],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
nonce.unwrap(),
|
||||
NonceFull::new(
|
||||
Pubkey::from([0; 32]),
|
||||
AccountSharedData::default(),
|
||||
Some(mock_bank.accounts_map[&key2.pubkey()].clone())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_error() {
|
||||
let mock_bank = TestCallbacks::default();
|
||||
let message = Message {
|
||||
account_keys: vec![Pubkey::new_from_array([0; 32])],
|
||||
header: MessageHeader::default(),
|
||||
instructions: vec![CompiledInstruction {
|
||||
program_id_index: 0,
|
||||
accounts: vec![],
|
||||
data: vec![],
|
||||
}],
|
||||
recent_blockhash: Hash::default(),
|
||||
};
|
||||
|
||||
let legacy = LegacyMessage::new(message);
|
||||
let sanitized_message = SanitizedMessage::Legacy(legacy);
|
||||
let sanitized_transaction = SanitizedTransaction::new_for_tests(
|
||||
sanitized_message,
|
||||
vec![Signature::new_unique()],
|
||||
false,
|
||||
);
|
||||
|
||||
let lock_results = (Ok(()), Some(NoncePartial::default()), None) as TransactionCheckResult;
|
||||
let fee_structure = FeeStructure::default();
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![(Err(TransactionError::BlockhashNotFound), None)]
|
||||
);
|
||||
|
||||
let lock_results =
|
||||
(Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult;
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results.clone()],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(result, vec![(Err(TransactionError::AccountNotFound), None)]);
|
||||
|
||||
let lock_results = (
|
||||
Err(TransactionError::InvalidWritableAccount),
|
||||
Some(NoncePartial::default()),
|
||||
Some(20u64),
|
||||
) as TransactionCheckResult;
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![(Err(TransactionError::InvalidWritableAccount), None)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ pub struct AccountOverrides {
|
|||
}
|
||||
|
||||
impl AccountOverrides {
|
||||
/// Insert or remove an account with a given pubkey to/from the list of overrides.
|
||||
pub fn set_account(&mut self, pubkey: &Pubkey, account: Option<AccountSharedData>) {
|
||||
match account {
|
||||
Some(account) => self.accounts.insert(*pubkey, account),
|
||||
|
|
|
@ -23,6 +23,7 @@ pub enum RentState {
|
|||
}
|
||||
|
||||
impl RentState {
|
||||
/// Return a new RentState instance for a given account and rent.
|
||||
pub fn from_account(account: &AccountSharedData, rent: &Rent) -> Self {
|
||||
if account.lamports() == 0 {
|
||||
Self::Uninitialized
|
||||
|
@ -36,6 +37,8 @@ impl RentState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check whether a transition from the pre_rent_state to this
|
||||
/// state is valid.
|
||||
pub fn transition_allowed_from(&self, pre_rent_state: &RentState) -> bool {
|
||||
match self {
|
||||
Self::Uninitialized | Self::RentExempt => true,
|
||||
|
@ -57,21 +60,6 @@ impl RentState {
|
|||
}
|
||||
}
|
||||
|
||||
fn submit_rent_state_metrics(pre_rent_state: &Self, post_rent_state: &Self) {
|
||||
match (pre_rent_state, post_rent_state) {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_err-new_account", 1);
|
||||
}
|
||||
(&RentState::RentPaying { .. }, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_ok-legacy", 1);
|
||||
}
|
||||
(_, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_err-other", 1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_rent_state(
|
||||
pre_rent_state: Option<&Self>,
|
||||
post_rent_state: Option<&Self>,
|
||||
|
@ -118,6 +106,21 @@ impl RentState {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn submit_rent_state_metrics(pre_rent_state: &Self, post_rent_state: &Self) {
|
||||
match (pre_rent_state, post_rent_state) {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_err-new_account", 1);
|
||||
}
|
||||
(&RentState::RentPaying { .. }, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_ok-legacy", 1);
|
||||
}
|
||||
(_, &RentState::RentPaying { .. }) => {
|
||||
inc_new_counter_info!("rent_paying_err-other", 1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -11,12 +11,12 @@ use {
|
|||
};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct TransactionAccountStateInfo {
|
||||
pub(crate) struct TransactionAccountStateInfo {
|
||||
rent_state: Option<RentState>, // None: readonly account
|
||||
}
|
||||
|
||||
impl TransactionAccountStateInfo {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
rent: &Rent,
|
||||
transaction_context: &TransactionContext,
|
||||
message: &SanitizedMessage,
|
||||
|
|
|
@ -195,6 +195,7 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Main entrypoint to the SVM.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
|
@ -377,6 +378,112 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
|
|||
result
|
||||
}
|
||||
|
||||
/// Load program with a specific pubkey from loaded programs
|
||||
/// cache, and update the program's access slot as a side-effect.
|
||||
pub fn load_program_with_pubkey<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
reload: bool,
|
||||
effective_epoch: Epoch,
|
||||
) -> Arc<LoadedProgram> {
|
||||
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
|
||||
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 !Arc::ptr_eq(
|
||||
&environments.program_runtime_v1,
|
||||
&loaded_programs_cache.environments.program_runtime_v1,
|
||||
) || !Arc::ptr_eq(
|
||||
&environments.program_runtime_v2,
|
||||
&loaded_programs_cache.environments.program_runtime_v2,
|
||||
) {
|
||||
// There can be two entries per program when the environment changes.
|
||||
// One for the old environment before the epoch boundary and one for the new environment after the epoch boundary.
|
||||
// These two entries have the same deployment slot, so they must differ in their effective slot instead.
|
||||
// This is done by setting the effective slot of the entry for the new environment to the epoch boundary.
|
||||
loaded_program.effective_slot = loaded_program
|
||||
.effective_slot
|
||||
.max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch));
|
||||
}
|
||||
loaded_program.update_access_slot(self.slot);
|
||||
Arc::new(loaded_program)
|
||||
}
|
||||
|
||||
fn replenish_program_cache<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callback: &CB,
|
||||
|
@ -454,7 +561,7 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
|
|||
|
||||
if let Some((key, count)) = program_to_load {
|
||||
// Load, verify and compile one program.
|
||||
let program = self.load_program(callback, &key, false, self.epoch);
|
||||
let program = self.load_program_with_pubkey(callback, &key, false, self.epoch);
|
||||
program.tx_usage_counter.store(count, Ordering::Relaxed);
|
||||
program_to_store = Some((key, program));
|
||||
} else if missing_programs.is_empty() {
|
||||
|
@ -683,110 +790,6 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_program<CB: TransactionProcessingCallback>(
|
||||
&self,
|
||||
callbacks: &CB,
|
||||
pubkey: &Pubkey,
|
||||
reload: bool,
|
||||
effective_epoch: Epoch,
|
||||
) -> Arc<LoadedProgram> {
|
||||
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
|
||||
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 !Arc::ptr_eq(
|
||||
&environments.program_runtime_v1,
|
||||
&loaded_programs_cache.environments.program_runtime_v1,
|
||||
) || !Arc::ptr_eq(
|
||||
&environments.program_runtime_v2,
|
||||
&loaded_programs_cache.environments.program_runtime_v2,
|
||||
) {
|
||||
// There can be two entries per program when the environment changes.
|
||||
// One for the old environment before the epoch boundary and one for the new environment after the epoch boundary.
|
||||
// These two entries have the same deployment slot, so they must differ in their effective slot instead.
|
||||
// This is done by setting the effective slot of the entry for the new environment to the epoch boundary.
|
||||
loaded_program.effective_slot = loaded_program
|
||||
.effective_slot
|
||||
.max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch));
|
||||
}
|
||||
loaded_program.update_access_slot(self.slot);
|
||||
Arc::new(loaded_program)
|
||||
}
|
||||
|
||||
fn load_program_from_bytes(
|
||||
load_program_metrics: &mut LoadProgramMetrics,
|
||||
programdata: &[u8],
|
||||
|
@ -1242,7 +1245,7 @@ mod tests {
|
|||
let key = Pubkey::new_unique();
|
||||
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 50);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 50);
|
||||
|
||||
let loaded_program = LoadedProgram::new_tombstone(0, LoadedProgramType::Closed);
|
||||
assert_eq!(result, Arc::new(loaded_program));
|
||||
|
@ -1259,7 +1262,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20);
|
||||
|
||||
let loaded_program = LoadedProgram::new_tombstone(
|
||||
0,
|
||||
|
@ -1288,7 +1291,7 @@ mod tests {
|
|||
.insert(key, account_data.clone());
|
||||
|
||||
// This should return an error
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20);
|
||||
let loaded_program = LoadedProgram::new_tombstone(
|
||||
0,
|
||||
LoadedProgramType::FailedVerification(
|
||||
|
@ -1316,7 +1319,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20);
|
||||
|
||||
let environments = ProgramRuntimeEnvironments::default();
|
||||
let expected = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
|
||||
|
@ -1361,7 +1364,7 @@ mod tests {
|
|||
.insert(key2, account_data2.clone());
|
||||
|
||||
// This should return an error
|
||||
let result = batch_processor.load_program(&mock_bank, &key1, false, 0);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key1, false, 0);
|
||||
let loaded_program = LoadedProgram::new_tombstone(
|
||||
0,
|
||||
LoadedProgramType::FailedVerification(
|
||||
|
@ -1399,7 +1402,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key2, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key1, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key1, false, 20);
|
||||
|
||||
let data = account_data.data();
|
||||
account_data
|
||||
|
@ -1441,7 +1444,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 0);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 0);
|
||||
let loaded_program = LoadedProgram::new_tombstone(
|
||||
0,
|
||||
LoadedProgramType::FailedVerification(
|
||||
|
@ -1475,7 +1478,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20);
|
||||
|
||||
let data = account_data.data()[LoaderV4State::program_data_offset()..].to_vec();
|
||||
account_data.set_data(data);
|
||||
|
@ -1513,7 +1516,7 @@ mod tests {
|
|||
.account_shared_data
|
||||
.insert(key, account_data.clone());
|
||||
|
||||
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
|
||||
let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20);
|
||||
|
||||
let slot = batch_processor.epoch_schedule.get_first_slot_in_epoch(20);
|
||||
assert_eq!(result.effective_slot, slot);
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
use {
|
||||
crate::mock_bank::MockBankCallback,
|
||||
solana_program_runtime::loaded_programs::LoadedProgramsForTxBatch,
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, WritableAccount},
|
||||
fee::FeeStructure,
|
||||
hash::Hash,
|
||||
instruction::CompiledInstruction,
|
||||
message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
|
||||
native_loader,
|
||||
nonce_info::{NonceFull, NoncePartial},
|
||||
pubkey::Pubkey,
|
||||
rent_collector::RENT_EXEMPT_RENT_EPOCH,
|
||||
rent_debits::RentDebits,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
transaction::{SanitizedTransaction, TransactionError},
|
||||
},
|
||||
solana_svm::{
|
||||
account_loader::{load_accounts, LoadedTransaction, TransactionCheckResult},
|
||||
transaction_error_metrics::TransactionErrorMetrics,
|
||||
},
|
||||
std::collections::HashMap,
|
||||
};
|
||||
|
||||
mod mock_bank;
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_success() {
|
||||
let key1 = Keypair::new();
|
||||
let key2 = Keypair::new();
|
||||
let key3 = Keypair::new();
|
||||
let key4 = Keypair::new();
|
||||
|
||||
let message = Message {
|
||||
account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()],
|
||||
header: MessageHeader::default(),
|
||||
instructions: vec![
|
||||
CompiledInstruction {
|
||||
program_id_index: 1,
|
||||
accounts: vec![0],
|
||||
data: vec![],
|
||||
},
|
||||
CompiledInstruction {
|
||||
program_id_index: 1,
|
||||
accounts: vec![2],
|
||||
data: vec![],
|
||||
},
|
||||
],
|
||||
recent_blockhash: Hash::default(),
|
||||
};
|
||||
|
||||
let legacy = LegacyMessage::new(message);
|
||||
let sanitized_message = SanitizedMessage::Legacy(legacy);
|
||||
let mut mock_bank = MockBankCallback::default();
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_executable(true);
|
||||
account_data.set_owner(key3.pubkey());
|
||||
mock_bank
|
||||
.account_shared_data
|
||||
.insert(key1.pubkey(), account_data);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_lamports(200);
|
||||
mock_bank
|
||||
.account_shared_data
|
||||
.insert(key2.pubkey(), account_data);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_executable(true);
|
||||
account_data.set_owner(native_loader::id());
|
||||
mock_bank
|
||||
.account_shared_data
|
||||
.insert(key3.pubkey(), account_data);
|
||||
|
||||
let mut error_counter = TransactionErrorMetrics::default();
|
||||
let loaded_programs = LoadedProgramsForTxBatch::default();
|
||||
|
||||
let sanitized_transaction = SanitizedTransaction::new_for_tests(
|
||||
sanitized_message,
|
||||
vec![Signature::new_unique()],
|
||||
false,
|
||||
);
|
||||
let lock_results =
|
||||
(Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult;
|
||||
|
||||
let results = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction],
|
||||
&[lock_results],
|
||||
&mut error_counter,
|
||||
&FeeStructure::default(),
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&loaded_programs,
|
||||
);
|
||||
|
||||
let mut account_data = AccountSharedData::default();
|
||||
account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
let (loaded_result, nonce) = results[0].clone();
|
||||
assert_eq!(
|
||||
loaded_result.unwrap(),
|
||||
LoadedTransaction {
|
||||
accounts: vec![
|
||||
(
|
||||
key2.pubkey(),
|
||||
mock_bank.account_shared_data[&key2.pubkey()].clone()
|
||||
),
|
||||
(
|
||||
key1.pubkey(),
|
||||
mock_bank.account_shared_data[&key1.pubkey()].clone()
|
||||
),
|
||||
(key4.pubkey(), account_data),
|
||||
(
|
||||
key3.pubkey(),
|
||||
mock_bank.account_shared_data[&key3.pubkey()].clone()
|
||||
),
|
||||
],
|
||||
program_indices: vec![vec![3, 1], vec![3, 1]],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
nonce.unwrap(),
|
||||
NonceFull::new(
|
||||
Pubkey::from([0; 32]),
|
||||
AccountSharedData::default(),
|
||||
Some(mock_bank.account_shared_data[&key2.pubkey()].clone())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_error() {
|
||||
let mock_bank = MockBankCallback::default();
|
||||
let message = Message {
|
||||
account_keys: vec![Pubkey::new_from_array([0; 32])],
|
||||
header: MessageHeader::default(),
|
||||
instructions: vec![CompiledInstruction {
|
||||
program_id_index: 0,
|
||||
accounts: vec![],
|
||||
data: vec![],
|
||||
}],
|
||||
recent_blockhash: Hash::default(),
|
||||
};
|
||||
|
||||
let legacy = LegacyMessage::new(message);
|
||||
let sanitized_message = SanitizedMessage::Legacy(legacy);
|
||||
let sanitized_transaction = SanitizedTransaction::new_for_tests(
|
||||
sanitized_message,
|
||||
vec![Signature::new_unique()],
|
||||
false,
|
||||
);
|
||||
|
||||
let lock_results = (Ok(()), Some(NoncePartial::default()), None) as TransactionCheckResult;
|
||||
let fee_structure = FeeStructure::default();
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![(Err(TransactionError::BlockhashNotFound), None)]
|
||||
);
|
||||
|
||||
let lock_results =
|
||||
(Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult;
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results.clone()],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(result, vec![(Err(TransactionError::AccountNotFound), None)]);
|
||||
|
||||
let lock_results = (
|
||||
Err(TransactionError::InvalidWritableAccount),
|
||||
Some(NoncePartial::default()),
|
||||
Some(20u64),
|
||||
) as TransactionCheckResult;
|
||||
|
||||
let result = load_accounts(
|
||||
&mock_bank,
|
||||
&[sanitized_transaction.clone()],
|
||||
&[lock_results],
|
||||
&mut TransactionErrorMetrics::default(),
|
||||
&fee_structure,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![(Err(TransactionError::InvalidWritableAccount), None)]
|
||||
);
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use {
|
||||
solana_program_runtime::{
|
||||
compute_budget::ComputeBudget, compute_budget_processor,
|
||||
loaded_programs::LoadedProgramsForTxBatch,
|
||||
},
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, WritableAccount},
|
||||
fee::FeeStructure,
|
||||
hash::Hash,
|
||||
native_loader,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
transaction::SanitizedTransaction,
|
||||
transaction_context::TransactionContext,
|
||||
},
|
||||
solana_svm::{
|
||||
account_loader::load_accounts, transaction_account_state_info::TransactionAccountStateInfo,
|
||||
transaction_error_metrics::TransactionErrorMetrics,
|
||||
},
|
||||
std::collections::HashMap,
|
||||
};
|
||||
|
||||
mod mock_bank;
|
||||
|
||||
#[test]
|
||||
fn test_rent_state_list_len() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mut bank = mock_bank::MockBankCallback::default();
|
||||
let recipient = Pubkey::new_unique();
|
||||
let last_block_hash = Hash::new_unique();
|
||||
|
||||
let mut system_data = AccountSharedData::default();
|
||||
system_data.set_executable(true);
|
||||
system_data.set_owner(native_loader::id());
|
||||
bank.account_shared_data
|
||||
.insert(Pubkey::new_from_array([0u8; 32]), system_data);
|
||||
|
||||
let mut mint_data = AccountSharedData::default();
|
||||
mint_data.set_lamports(2);
|
||||
bank.account_shared_data
|
||||
.insert(mint_keypair.pubkey(), mint_data);
|
||||
|
||||
bank.account_shared_data
|
||||
.insert(recipient, AccountSharedData::default());
|
||||
|
||||
let tx = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&recipient,
|
||||
sol_to_lamports(1.),
|
||||
last_block_hash,
|
||||
);
|
||||
let num_accounts = tx.message().account_keys.len();
|
||||
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx);
|
||||
let mut error_counters = TransactionErrorMetrics::default();
|
||||
let loaded_txs = load_accounts(
|
||||
&bank,
|
||||
&[sanitized_tx.clone()],
|
||||
&[(Ok(()), None, Some(0))],
|
||||
&mut error_counters,
|
||||
&FeeStructure::default(),
|
||||
None,
|
||||
&HashMap::new(),
|
||||
&LoadedProgramsForTxBatch::default(),
|
||||
);
|
||||
|
||||
let compute_budget = ComputeBudget::new(u64::from(
|
||||
compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
|
||||
));
|
||||
let transaction_context = TransactionContext::new(
|
||||
loaded_txs[0].0.as_ref().unwrap().accounts.clone(),
|
||||
Rent::default(),
|
||||
compute_budget.max_invoke_stack_height,
|
||||
compute_budget.max_instruction_trace_length,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TransactionAccountStateInfo::new(
|
||||
&Rent::default(),
|
||||
&transaction_context,
|
||||
sanitized_tx.message()
|
||||
)
|
||||
.len(),
|
||||
num_accounts,
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue