Add tests for `svm/transaction_processor.rs` (#112)

This commit is contained in:
Lucas Steuernagel 2024-03-07 12:26:31 -03:00 committed by GitHub
parent 8f3f06cc7f
commit 85cfe23b46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 531 additions and 3 deletions

1
Cargo.lock generated
View File

@ -7288,6 +7288,7 @@ dependencies = [
name = "solana-svm"
version = "1.19.0"
dependencies = [
"bincode",
"itertools",
"log",
"percentage",

View File

@ -39,7 +39,7 @@ use {
},
instruction::{AccountMeta, InstructionError},
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
native_loader,
loader_v4, native_loader,
program_utils::limited_deserialize,
pubkey::Pubkey,
saturating_add_assign,
@ -193,6 +193,7 @@ pub fn check_loader_id(id: &Pubkey) -> bool {
bpf_loader::check_id(id)
|| bpf_loader_deprecated::check_id(id)
|| bpf_loader_upgradeable::check_id(id)
|| loader_v4::check_id(id)
}
/// Only used in macro, do not use directly!

View File

@ -28,6 +28,7 @@ crate-type = ["lib"]
name = "solana_svm"
[dev-dependencies]
bincode = { workspace = true }
solana-logger = { workspace = true }
solana-sdk = { workspace = true, features = ["dev-context-only-utils"] }

View File

@ -87,6 +87,7 @@ pub trait TransactionProcessingCallback {
}
}
#[derive(Debug)]
enum ProgramAccountLoadResult {
AccountNotFound,
InvalidAccountData(ProgramRuntimeEnvironment),
@ -921,8 +922,18 @@ impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
mod tests {
use {
super::*,
solana_program_runtime::loaded_programs::BlockRelation,
solana_sdk::{sysvar::rent::Rent, transaction_context::TransactionContext},
solana_program_runtime::{
loaded_programs::BlockRelation, solana_rbpf::program::BuiltinProgram,
},
solana_sdk::{
account::WritableAccount, bpf_loader, sysvar::rent::Rent,
transaction_context::TransactionContext,
},
std::{
env,
fs::{self, File},
io::Read,
},
};
struct TestForkGraph {}
@ -933,6 +944,43 @@ mod tests {
}
}
#[derive(Default)]
pub struct MockBankCallback {
rent_collector: RentCollector,
feature_set: Arc<FeatureSet>,
pub account_shared_data: HashMap<Pubkey, AccountSharedData>,
}
impl TransactionProcessingCallback for MockBankCallback {
fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option<usize> {
if let Some(data) = self.account_shared_data.get(account) {
if data.lamports() == 0 {
None
} else {
owners.iter().position(|entry| data.owner() == entry)
}
} else {
None
}
}
fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.account_shared_data.get(pubkey).cloned()
}
fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) {
todo!()
}
fn get_rent_collector(&self) -> &RentCollector {
&self.rent_collector
}
fn get_feature_set(&self) -> Arc<FeatureSet> {
self.feature_set.clone()
}
}
#[test]
fn test_inner_instructions_list_from_instruction_trace() {
let instruction_trace = [1, 2, 1, 1, 2, 3, 2];
@ -980,4 +1028,481 @@ mod tests {
]
);
}
#[test]
fn test_load_program_accounts_account_not_found() {
let mut mock_bank = MockBankCallback::default();
let key = Pubkey::new_unique();
let environment = ProgramRuntimeEnvironments::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
assert!(matches!(result, ProgramAccountLoadResult::AccountNotFound));
let mut account_data = AccountSharedData::default();
account_data.set_owner(bpf_loader_upgradeable::id());
let state = UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
};
account_data.set_data(bincode::serialize(&state).unwrap());
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
assert!(matches!(result, ProgramAccountLoadResult::AccountNotFound));
account_data.set_data(Vec::new());
mock_bank.account_shared_data.insert(key, account_data);
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
assert!(matches!(
result,
ProgramAccountLoadResult::InvalidAccountData(_)
));
}
#[test]
fn test_load_program_accounts_loader_v4() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(loader_v4::id());
let environment = ProgramRuntimeEnvironments::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
assert!(matches!(
result,
ProgramAccountLoadResult::InvalidAccountData(_)
));
account_data.set_data(vec![0; 64]);
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
assert!(matches!(
result,
ProgramAccountLoadResult::InvalidAccountData(_)
));
let loader_data = LoaderV4State {
slot: 25,
authority_address: Pubkey::new_unique(),
status: LoaderV4Status::Deployed,
};
let encoded = unsafe {
std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(
&loader_data,
)
};
account_data.set_data(encoded.to_vec());
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
match result {
ProgramAccountLoadResult::ProgramOfLoaderV4(data, slot) => {
assert_eq!(data, account_data);
assert_eq!(slot, 25);
}
_ => panic!("Invalid result"),
}
}
#[test]
fn test_load_program_accounts_loader_v1_or_v2() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(bpf_loader::id());
let environment = ProgramRuntimeEnvironments::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key, &environment);
match result {
ProgramAccountLoadResult::ProgramOfLoaderV1orV2(data) => {
assert_eq!(data, account_data);
}
_ => panic!("Invalid result"),
}
}
#[test]
fn test_load_program_accounts_success() {
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let environment = ProgramRuntimeEnvironments::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(bpf_loader_upgradeable::id());
let state = UpgradeableLoaderState::Program {
programdata_address: key2,
};
account_data.set_data(bincode::serialize(&state).unwrap());
mock_bank
.account_shared_data
.insert(key1, account_data.clone());
let state = UpgradeableLoaderState::ProgramData {
slot: 25,
upgrade_authority_address: None,
};
let mut account_data2 = AccountSharedData::default();
account_data2.set_data(bincode::serialize(&state).unwrap());
mock_bank
.account_shared_data
.insert(key2, account_data2.clone());
let result = batch_processor.load_program_accounts(&mock_bank, &key1, &environment);
match result {
ProgramAccountLoadResult::ProgramOfLoaderV3(data1, data2, slot) => {
assert_eq!(data1, account_data);
assert_eq!(data2, account_data2);
assert_eq!(slot, 25);
}
_ => panic!("Invalid result"),
}
}
#[test]
fn test_load_program_from_bytes() {
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("test_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("Buffer overflow");
let mut metrics = LoadProgramMetrics::default();
let loader = bpf_loader_upgradeable::id();
let size = metadata.len() as usize;
let slot = 2;
let environment = ProgramRuntimeEnvironment::new(BuiltinProgram::new_mock());
let result = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
&mut metrics,
&buffer,
&loader,
size,
slot,
environment.clone(),
false,
);
assert!(result.is_ok());
let result = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
&mut metrics,
&buffer,
&loader,
size,
slot,
environment,
true,
);
assert!(result.is_ok());
}
#[test]
fn test_load_program_not_found() {
let mock_bank = MockBankCallback::default();
let key = Pubkey::new_unique();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
let result = batch_processor.load_program(&mock_bank, &key, false, 50);
let loaded_program = LoadedProgram::new_tombstone(0, LoadedProgramType::Closed);
assert_eq!(result, Arc::new(loaded_program));
}
#[test]
fn test_load_program_invalid_account_data() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(loader_v4::id());
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
let loaded_program = LoadedProgram::new_tombstone(
0,
LoadedProgramType::FailedVerification(
batch_processor
.loaded_programs_cache
.read()
.unwrap()
.get_environments_for_epoch(20)
.clone()
.program_runtime_v1,
),
);
assert_eq!(result, Arc::new(loaded_program));
}
#[test]
fn test_load_program_program_loader_v1_or_v2() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(bpf_loader::id());
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
mock_bank
.account_shared_data
.insert(key, account_data.clone());
// This should return an error
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
let loaded_program = LoadedProgram::new_tombstone(
0,
LoadedProgramType::FailedVerification(
batch_processor
.loaded_programs_cache
.read()
.unwrap()
.get_environments_for_epoch(20)
.clone()
.program_runtime_v1,
),
);
assert_eq!(result, Arc::new(loaded_program));
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("test_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("buffer overflow");
account_data.set_data(buffer);
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
let environments = ProgramRuntimeEnvironments::default();
let expected = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
&mut LoadProgramMetrics::default(),
account_data.data(),
account_data.owner(),
account_data.data().len(),
0,
environments.program_runtime_v1.clone(),
false,
);
assert_eq!(result, Arc::new(expected.unwrap()));
}
#[test]
fn test_load_program_program_loader_v3() {
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(bpf_loader_upgradeable::id());
let state = UpgradeableLoaderState::Program {
programdata_address: key2,
};
account_data.set_data(bincode::serialize(&state).unwrap());
mock_bank
.account_shared_data
.insert(key1, account_data.clone());
let state = UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: None,
};
let mut account_data2 = AccountSharedData::default();
account_data2.set_data(bincode::serialize(&state).unwrap());
mock_bank
.account_shared_data
.insert(key2, account_data2.clone());
// This should return an error
let result = batch_processor.load_program(&mock_bank, &key1, false, 0);
let loaded_program = LoadedProgram::new_tombstone(
0,
LoadedProgramType::FailedVerification(
batch_processor
.loaded_programs_cache
.read()
.unwrap()
.get_environments_for_epoch(0)
.clone()
.program_runtime_v1,
),
);
assert_eq!(result, Arc::new(loaded_program));
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("test_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("buffer overflow");
let mut header = bincode::serialize(&state).unwrap();
let mut complement = vec![
0;
std::cmp::max(
0,
UpgradeableLoaderState::size_of_programdata_metadata() - header.len()
)
];
header.append(&mut complement);
header.append(&mut buffer);
account_data.set_data(header);
mock_bank
.account_shared_data
.insert(key2, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key1, false, 20);
let data = account_data.data();
account_data
.set_data(data[UpgradeableLoaderState::size_of_programdata_metadata()..].to_vec());
let environments = ProgramRuntimeEnvironments::default();
let expected = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
&mut LoadProgramMetrics::default(),
account_data.data(),
account_data.owner(),
account_data.data().len(),
0,
environments.program_runtime_v1.clone(),
false,
);
assert_eq!(result, Arc::new(expected.unwrap()));
}
#[test]
fn test_load_program_of_loader_v4() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(loader_v4::id());
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
let loader_data = LoaderV4State {
slot: 0,
authority_address: Pubkey::new_unique(),
status: LoaderV4Status::Deployed,
};
let encoded = unsafe {
std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(
&loader_data,
)
};
account_data.set_data(encoded.to_vec());
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key, false, 0);
let loaded_program = LoadedProgram::new_tombstone(
0,
LoadedProgramType::FailedVerification(
batch_processor
.loaded_programs_cache
.read()
.unwrap()
.get_environments_for_epoch(0)
.clone()
.program_runtime_v1,
),
);
assert_eq!(result, Arc::new(loaded_program));
let mut header = account_data.data().to_vec();
let mut complement =
vec![0; std::cmp::max(0, LoaderV4State::program_data_offset() - header.len())];
header.append(&mut complement);
let mut dir = env::current_dir().unwrap();
dir.push("tests");
dir.push("test_program.so");
let mut file = File::open(dir.clone()).expect("file not found");
let metadata = fs::metadata(dir).expect("Unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer).expect("buffer overflow");
header.append(&mut buffer);
account_data.set_data(header);
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
let data = account_data.data()[LoaderV4State::program_data_offset()..].to_vec();
account_data.set_data(data);
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let environments = ProgramRuntimeEnvironments::default();
let expected = TransactionBatchProcessor::<TestForkGraph>::load_program_from_bytes(
&mut LoadProgramMetrics::default(),
account_data.data(),
account_data.owner(),
account_data.data().len(),
0,
environments.program_runtime_v1.clone(),
false,
);
assert_eq!(result, Arc::new(expected.unwrap()));
}
#[test]
fn test_load_program_effective_slot() {
let key = Pubkey::new_unique();
let mut mock_bank = MockBankCallback::default();
let mut account_data = AccountSharedData::default();
account_data.set_owner(loader_v4::id());
let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
batch_processor
.loaded_programs_cache
.write()
.unwrap()
.upcoming_environments = Some(ProgramRuntimeEnvironments::default());
mock_bank
.account_shared_data
.insert(key, account_data.clone());
let result = batch_processor.load_program(&mock_bank, &key, false, 20);
let slot = batch_processor.epoch_schedule.get_first_slot_in_epoch(20);
assert_eq!(result.effective_slot, slot);
}
}

BIN
svm/tests/test_program.so Executable file

Binary file not shown.