Add more unit tests to SVM (#35383)

This commit is contained in:
Lucas Steuernagel 2024-03-01 16:04:08 -03:00 committed by GitHub
parent 7c878973e2
commit 532b806bef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 483 additions and 0 deletions

View File

@ -106,6 +106,8 @@ pub fn load_accounts<CB: TransactionProcessingCallback>(
&loaded_transaction.rent_debits,
) {
Ok(nonce) => Some(nonce),
// This error branch is never reached, because `load_transaction_accounts`
// already validates the fee payer account.
Err(e) => return (Err(e), None),
}
} else {

View File

@ -29,3 +29,34 @@ impl AccountOverrides {
self.accounts.get(pubkey)
}
}
#[cfg(test)]
mod test {
use {
crate::account_overrides::AccountOverrides,
solana_sdk::{account::AccountSharedData, pubkey::Pubkey, sysvar},
};
#[test]
fn test_set_account() {
let mut accounts = AccountOverrides::default();
let data = AccountSharedData::default();
let key = Pubkey::new_unique();
accounts.set_account(&key, Some(data.clone()));
assert_eq!(accounts.get(&key), Some(&data));
accounts.set_account(&key, None);
assert!(accounts.get(&key).is_none());
}
#[test]
fn test_slot_history() {
let mut accounts = AccountOverrides::default();
let data = AccountSharedData::default();
assert_eq!(accounts.get(&sysvar::slot_history::id()), None);
accounts.set_slot_history(Some(data.clone()));
assert_eq!(accounts.get(&sysvar::slot_history::id()), Some(&data));
}
}

View File

@ -237,4 +237,71 @@ mod tests {
}),
);
}
#[test]
fn test_check_rent_state_with_account() {
let pre_rent_state = RentState::RentPaying {
data_size: 2,
lamports: 3,
};
let post_rent_state = RentState::RentPaying {
data_size: 2,
lamports: 5,
};
let account_index = 2 as IndexOfAccount;
let key = Pubkey::new_unique();
let result = RentState::check_rent_state_with_account(
&pre_rent_state,
&post_rent_state,
&key,
&AccountSharedData::default(),
account_index,
);
assert_eq!(
result.err(),
Some(TransactionError::InsufficientFundsForRent {
account_index: account_index as u8
})
);
let result = RentState::check_rent_state_with_account(
&pre_rent_state,
&post_rent_state,
&solana_sdk::incinerator::id(),
&AccountSharedData::default(),
account_index,
);
assert!(result.is_ok());
}
#[test]
fn test_check_rent_state() {
let context = TransactionContext::new(
vec![(Pubkey::new_unique(), AccountSharedData::default())],
Rent::default(),
20,
20,
);
let pre_rent_state = RentState::RentPaying {
data_size: 2,
lamports: 3,
};
let post_rent_state = RentState::RentPaying {
data_size: 2,
lamports: 5,
};
let result =
RentState::check_rent_state(Some(&pre_rent_state), Some(&post_rent_state), &context, 0);
assert_eq!(
result.err(),
Some(TransactionError::InsufficientFundsForRent { account_index: 0 })
);
let result = RentState::check_rent_state(None, Some(&post_rent_state), &context, 0);
assert!(result.is_ok());
}
}

View File

@ -10,6 +10,7 @@ use {
},
};
#[derive(PartialEq, Debug)]
pub struct TransactionAccountStateInfo {
rent_state: Option<RentState>, // None: readonly account
}
@ -67,3 +68,171 @@ impl TransactionAccountStateInfo {
Ok(())
}
}
#[cfg(test)]
mod test {
use {
crate::{
account_rent_state::RentState,
transaction_account_state_info::TransactionAccountStateInfo,
},
solana_sdk::{
account::AccountSharedData,
hash::Hash,
instruction::CompiledInstruction,
message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
rent::Rent,
signature::{Keypair, Signer},
transaction::TransactionError,
transaction_context::TransactionContext,
},
};
#[test]
fn test_new() {
let rent = Rent::default();
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 transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
(key3.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20);
let result = TransactionAccountStateInfo::new(&rent, &context, &sanitized_message);
assert_eq!(
result,
vec![
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized)
},
TransactionAccountStateInfo { rent_state: None },
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized)
}
]
);
}
#[test]
#[should_panic(expected = "message and transaction context out of sync, fatal")]
fn test_new_panic() {
let rent = Rent::default();
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(), key3.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 transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
(key3.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20);
let _result = TransactionAccountStateInfo::new(&rent, &context, &sanitized_message);
}
#[test]
fn test_verify_changes() {
let key1 = Keypair::new();
let key2 = Keypair::new();
let pre_rent_state = vec![
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
},
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
},
];
let post_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert!(result.is_ok());
let pre_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
}];
let post_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::RentPaying {
data_size: 2,
lamports: 5,
}),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert_eq!(
result.err(),
Some(TransactionError::InsufficientFundsForRent { account_index: 0 })
);
}
}

214
svm/tests/account_loader.rs Normal file
View File

@ -0,0 +1,214 @@
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)]
);
}