SVM: Add doc comments, restrict visibility of some xfaces to crate (#136)

This commit is contained in:
Dmitri Makarov 2024-03-08 14:04:07 -05:00 committed by GitHub
parent bf0a3684eb
commit d88050cda3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 463 additions and 497 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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