Skip to content

Commit e8526f6

Browse files
authored
SVM integration test (pyth-network#307)
1 parent d4bcdf8 commit e8526f6

File tree

5 files changed

+278
-5
lines changed

5 files changed

+278
-5
lines changed

svm/src/transaction_processor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,7 @@ mod tests {
12151215
fn test_load_program_from_bytes() {
12161216
let mut dir = env::current_dir().unwrap();
12171217
dir.push("tests");
1218-
dir.push("test_program.so");
1218+
dir.push("hello_solana_program.so");
12191219
let mut file = File::open(dir.clone()).expect("file not found");
12201220
let metadata = fs::metadata(dir).expect("Unable to read metadata");
12211221
let mut buffer = vec![0; metadata.len() as usize];
@@ -1321,7 +1321,7 @@ mod tests {
13211321

13221322
let mut dir = env::current_dir().unwrap();
13231323
dir.push("tests");
1324-
dir.push("test_program.so");
1324+
dir.push("hello_solana_program.so");
13251325
let mut file = File::open(dir.clone()).expect("file not found");
13261326
let metadata = fs::metadata(dir).expect("Unable to read metadata");
13271327
let mut buffer = vec![0; metadata.len() as usize];
@@ -1394,7 +1394,7 @@ mod tests {
13941394

13951395
let mut dir = env::current_dir().unwrap();
13961396
dir.push("tests");
1397-
dir.push("test_program.so");
1397+
dir.push("hello_solana_program.so");
13981398
let mut file = File::open(dir.clone()).expect("file not found");
13991399
let metadata = fs::metadata(dir).expect("Unable to read metadata");
14001400
let mut buffer = vec![0; metadata.len() as usize];
@@ -1479,7 +1479,7 @@ mod tests {
14791479

14801480
let mut dir = env::current_dir().unwrap();
14811481
dir.push("tests");
1482-
dir.push("test_program.so");
1482+
dir.push("hello_solana_program.so");
14831483
let mut file = File::open(dir.clone()).expect("file not found");
14841484
let metadata = fs::metadata(dir).expect("Unable to read metadata");
14851485
let mut buffer = vec![0; metadata.len() as usize];

svm/tests/hello_solana_program.so

34.6 KB
Binary file not shown.

svm/tests/integration_test.rs

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#![cfg(test)]
2+
3+
use {
4+
crate::mock_bank::MockBankCallback,
5+
solana_bpf_loader_program::syscalls::{SyscallAbort, SyscallLog, SyscallMemcpy, SyscallMemset},
6+
solana_program_runtime::{
7+
compute_budget::ComputeBudget,
8+
invoke_context::InvokeContext,
9+
loaded_programs::{
10+
BlockRelation, ForkGraph, LoadedProgram, LoadedPrograms, ProgramRuntimeEnvironments,
11+
},
12+
runtime_config::RuntimeConfig,
13+
solana_rbpf::{
14+
program::{BuiltinFunction, BuiltinProgram, FunctionRegistry},
15+
vm::Config,
16+
},
17+
timings::ExecuteTimings,
18+
},
19+
solana_sdk::{
20+
account::{AccountSharedData, WritableAccount},
21+
bpf_loader,
22+
clock::{Epoch, Slot},
23+
epoch_schedule::EpochSchedule,
24+
fee::FeeStructure,
25+
hash::Hash,
26+
instruction::CompiledInstruction,
27+
message::{Message, MessageHeader},
28+
native_loader,
29+
pubkey::Pubkey,
30+
signature::Signature,
31+
transaction::{SanitizedTransaction, Transaction},
32+
},
33+
solana_svm::{
34+
account_loader::TransactionCheckResult,
35+
transaction_error_metrics::TransactionErrorMetrics,
36+
transaction_processor::{ExecutionRecordingConfig, TransactionBatchProcessor},
37+
},
38+
std::{
39+
cmp::Ordering,
40+
env,
41+
fs::{self, File},
42+
io::Read,
43+
sync::{Arc, RwLock},
44+
},
45+
};
46+
47+
// This module contains the implementation of TransactionProcessingCallback
48+
mod mock_bank;
49+
50+
const BPF_LOADER_NAME: &str = "solana_bpf_loader_program";
51+
const DEPLOYMENT_SLOT: u64 = 0;
52+
const EXECUTION_SLOT: u64 = 5; // The execution slot must be greater than the deployment slot
53+
const DEPLOYMENT_EPOCH: u64 = 0;
54+
const EXECUTION_EPOCH: u64 = 2; // The execution epoch must be greater than the deployment epoch
55+
56+
struct MockForkGraph {}
57+
58+
impl ForkGraph for MockForkGraph {
59+
fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
60+
match a.cmp(&b) {
61+
Ordering::Less => BlockRelation::Ancestor,
62+
Ordering::Equal => BlockRelation::Equal,
63+
Ordering::Greater => BlockRelation::Descendant,
64+
}
65+
}
66+
67+
fn slot_epoch(&self, _slot: Slot) -> Option<Epoch> {
68+
Some(0)
69+
}
70+
}
71+
72+
fn create_custom_environment<'a>() -> BuiltinProgram<InvokeContext<'a>> {
73+
let compute_budget = ComputeBudget::default();
74+
let vm_config = Config {
75+
max_call_depth: compute_budget.max_call_depth,
76+
stack_frame_size: compute_budget.stack_frame_size,
77+
enable_address_translation: true,
78+
enable_stack_frame_gaps: true,
79+
instruction_meter_checkpoint_distance: 10000,
80+
enable_instruction_meter: true,
81+
enable_instruction_tracing: true,
82+
enable_symbol_and_section_labels: true,
83+
reject_broken_elfs: true,
84+
noop_instruction_rate: 256,
85+
sanitize_user_provided_values: true,
86+
external_internal_function_hash_collision: false,
87+
reject_callx_r10: false,
88+
enable_sbpf_v1: true,
89+
enable_sbpf_v2: false,
90+
optimize_rodata: false,
91+
new_elf_parser: false,
92+
aligned_memory_mapping: true,
93+
};
94+
95+
// These functions are system calls the compile contract calls during execution, so they
96+
// need to be registered.
97+
let mut function_registry = FunctionRegistry::<BuiltinFunction<InvokeContext>>::default();
98+
function_registry
99+
.register_function_hashed(*b"abort", SyscallAbort::vm)
100+
.expect("Registration failed");
101+
function_registry
102+
.register_function_hashed(*b"sol_log_", SyscallLog::vm)
103+
.expect("Registration failed");
104+
function_registry
105+
.register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::vm)
106+
.expect("Registration failed");
107+
function_registry
108+
.register_function_hashed(*b"sol_memset_", SyscallMemset::vm)
109+
.expect("Registration failed");
110+
111+
BuiltinProgram::new_loader(vm_config, function_registry)
112+
}
113+
114+
fn create_executable_environment(
115+
mock_bank: &mut MockBankCallback,
116+
) -> (LoadedPrograms<MockForkGraph>, Vec<Pubkey>) {
117+
let mut programs_cache = LoadedPrograms::<MockForkGraph>::new(0, 20);
118+
119+
// We must register the bpf loader account as a loadable account, otherwise programs
120+
// won't execute.
121+
let account_data = native_loader::create_loadable_account_with_fields(
122+
BPF_LOADER_NAME,
123+
(5000, DEPLOYMENT_EPOCH),
124+
);
125+
mock_bank
126+
.account_shared_data
127+
.insert(bpf_loader::id(), account_data);
128+
129+
// The bpf loader needs an executable as well
130+
programs_cache.assign_program(
131+
bpf_loader::id(),
132+
Arc::new(LoadedProgram::new_builtin(
133+
DEPLOYMENT_SLOT,
134+
BPF_LOADER_NAME.len(),
135+
solana_bpf_loader_program::Entrypoint::vm,
136+
)),
137+
);
138+
139+
programs_cache.environments = ProgramRuntimeEnvironments {
140+
program_runtime_v1: Arc::new(create_custom_environment()),
141+
// We are not using program runtime v2
142+
program_runtime_v2: Arc::new(BuiltinProgram::new_loader(
143+
Config::default(),
144+
FunctionRegistry::default(),
145+
)),
146+
};
147+
148+
programs_cache.fork_graph = Some(Arc::new(RwLock::new(MockForkGraph {})));
149+
150+
// Inform SVM of the registered builins
151+
let registered_built_ins = vec![bpf_loader::id()];
152+
(programs_cache, registered_built_ins)
153+
}
154+
155+
fn prepare_transactions(
156+
mock_bank: &mut MockBankCallback,
157+
) -> (Vec<SanitizedTransaction>, Vec<TransactionCheckResult>) {
158+
let mut all_transactions = Vec::new();
159+
let mut transaction_checks = Vec::new();
160+
161+
// A transaction that works without any account
162+
let key1 = Pubkey::new_unique();
163+
let fee_payer = Pubkey::new_unique();
164+
let message = Message {
165+
account_keys: vec![fee_payer, key1],
166+
header: MessageHeader {
167+
num_required_signatures: 1,
168+
num_readonly_signed_accounts: 0,
169+
num_readonly_unsigned_accounts: 0,
170+
},
171+
instructions: vec![CompiledInstruction {
172+
program_id_index: 1,
173+
accounts: vec![],
174+
data: vec![],
175+
}],
176+
recent_blockhash: Hash::default(),
177+
};
178+
179+
let transaction = Transaction {
180+
signatures: vec![Signature::new_unique()],
181+
message,
182+
};
183+
let sanitized_transaction =
184+
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
185+
all_transactions.push(sanitized_transaction);
186+
transaction_checks.push((Ok(()), None, Some(20)));
187+
188+
// Loading the program file
189+
let mut dir = env::current_dir().unwrap();
190+
dir.push("tests");
191+
// File compiled from
192+
// https://github.com/solana-developers/program-examples/blob/feb82f254a4633ce2107d06060f2d0558dc987f5/basics/hello-solana/native/program/src/lib.rs
193+
dir.push("hello_solana_program.so");
194+
let mut file = File::open(dir.clone()).expect("file not found");
195+
let metadata = fs::metadata(dir).expect("Unable to read metadata");
196+
let mut buffer = vec![0; metadata.len() as usize];
197+
file.read_exact(&mut buffer).expect("Buffer overflow");
198+
199+
// The program account must have funds and hold the executable binary
200+
let mut account_data = AccountSharedData::default();
201+
// The executable account owner must be one of the loaders.
202+
account_data.set_owner(bpf_loader::id());
203+
account_data.set_data(buffer);
204+
account_data.set_executable(true);
205+
account_data.set_lamports(25);
206+
mock_bank.account_shared_data.insert(key1, account_data);
207+
208+
// The transaction fee payer must have enough funds
209+
let mut account_data = AccountSharedData::default();
210+
account_data.set_lamports(80000);
211+
mock_bank
212+
.account_shared_data
213+
.insert(fee_payer, account_data);
214+
215+
// TODO: Include these examples as well:
216+
// A simple funds transfer between accounts
217+
// A transaction that fails
218+
// A transaction whose verification has already failed
219+
220+
(all_transactions, transaction_checks)
221+
}
222+
223+
#[test]
224+
fn svm_integration() {
225+
let mut mock_bank = MockBankCallback::default();
226+
let (transactions, mut check_results) = prepare_transactions(&mut mock_bank);
227+
let (programs_cache, builtins) = create_executable_environment(&mut mock_bank);
228+
let programs_cache = Arc::new(RwLock::new(programs_cache));
229+
let batch_processor = TransactionBatchProcessor::<MockForkGraph>::new(
230+
EXECUTION_SLOT,
231+
EXECUTION_EPOCH,
232+
EpochSchedule::default(),
233+
FeeStructure::default(),
234+
Arc::new(RuntimeConfig::default()),
235+
programs_cache.clone(),
236+
);
237+
238+
let mut error_counter = TransactionErrorMetrics::default();
239+
let recording_config = ExecutionRecordingConfig {
240+
enable_log_recording: true,
241+
enable_return_data_recording: false,
242+
enable_cpi_recording: false,
243+
};
244+
let mut timings = ExecuteTimings::default();
245+
246+
let result = batch_processor.load_and_execute_sanitized_transactions(
247+
&mock_bank,
248+
&transactions,
249+
check_results.as_mut_slice(),
250+
&mut error_counter,
251+
recording_config,
252+
&mut timings,
253+
None,
254+
builtins.iter(),
255+
None,
256+
false,
257+
);
258+
259+
assert_eq!(result.execution_results.len(), 1);
260+
assert!(result.execution_results[0]
261+
.details()
262+
.unwrap()
263+
.status
264+
.is_ok());
265+
let logs = result.execution_results[0]
266+
.details()
267+
.unwrap()
268+
.log_messages
269+
.as_ref()
270+
.unwrap();
271+
assert!(logs.contains(&"Program log: Hello, Solana!".to_string()));
272+
}

svm/tests/mock_bank.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ impl TransactionProcessingCallback for MockBankCallback {
3535
}
3636

3737
fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) {
38-
todo!()
38+
// Mock a hash and a value
39+
(Hash::new_unique(), 2)
3940
}
4041

4142
fn get_rent_collector(&self) -> &RentCollector {

svm/tests/test_program.so

-166 KB
Binary file not shown.

0 commit comments

Comments
 (0)