1#![allow(clippy::arithmetic_side_effects)]
3
4pub use tokio;
6use {
7 agave_feature_set::FEATURE_NAMES,
8 async_trait::async_trait,
9 base64::{prelude::BASE64_STANDARD, Engine},
10 chrono_humanize::{Accuracy, HumanTime, Tense},
11 log::*,
12 solana_account::{create_account_shared_data_for_test, Account, AccountSharedData},
13 solana_account_info::AccountInfo,
14 solana_accounts_db::epoch_accounts_hash::EpochAccountsHash,
15 solana_banks_client::start_client,
16 solana_banks_server::banks_server::start_local_server,
17 solana_clock::{Epoch, Slot},
18 solana_compute_budget::compute_budget::ComputeBudget,
19 solana_fee_calculator::{FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE},
20 solana_genesis_config::{ClusterType, GenesisConfig},
21 solana_hash::Hash,
22 solana_instruction::{
23 error::{InstructionError, UNSUPPORTED_SYSVAR},
24 Instruction,
25 },
26 solana_keypair::Keypair,
27 solana_log_collector::ic_msg,
28 solana_native_token::sol_to_lamports,
29 solana_poh_config::PohConfig,
30 solana_program_entrypoint::{deserialize, SUCCESS},
31 solana_program_error::{ProgramError, ProgramResult},
32 solana_program_runtime::{
33 invoke_context::BuiltinFunctionWithContext, loaded_programs::ProgramCacheEntry,
34 serialization::serialize_parameters, stable_log,
35 },
36 solana_pubkey::Pubkey,
37 solana_rent::Rent,
38 solana_runtime::{
39 accounts_background_service::SnapshotRequestKind,
40 bank::Bank,
41 bank_forks::BankForks,
42 commitment::BlockCommitmentCache,
43 genesis_utils::{create_genesis_config_with_leader_ex, GenesisConfigInfo},
44 runtime_config::RuntimeConfig,
45 snapshot_config::SnapshotConfig,
46 snapshot_controller::SnapshotController,
47 },
48 solana_signer::Signer,
49 solana_stable_layout::stable_instruction::StableInstruction,
50 solana_sysvar::Sysvar,
51 solana_sysvar_id::SysvarId,
52 solana_timings::ExecuteTimings,
53 solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
54 std::{
55 cell::RefCell,
56 collections::{HashMap, HashSet},
57 convert::TryFrom,
58 fs::File,
59 io::{self, Read},
60 mem::transmute,
61 panic::AssertUnwindSafe,
62 path::{Path, PathBuf},
63 sync::{
64 atomic::{AtomicBool, Ordering},
65 Arc, RwLock,
66 },
67 time::{Duration, Instant},
68 },
69 thiserror::Error,
70 tokio::task::JoinHandle,
71};
72pub use {
74 solana_banks_client::{BanksClient, BanksClientError},
75 solana_banks_interface::BanksTransactionResultWithMetadata,
76 solana_program_runtime::invoke_context::InvokeContext,
77 solana_sbpf::{
78 error::EbpfError,
79 vm::{get_runtime_environment_key, EbpfVm},
80 },
81 solana_transaction_context::IndexOfAccount,
82};
83
84pub mod programs;
85
86#[derive(Error, Debug, PartialEq, Eq)]
88pub enum ProgramTestError {
89 #[error("Warp slot not in the future")]
91 InvalidWarpSlot,
92}
93
94thread_local! {
95 static INVOKE_CONTEXT: RefCell<Option<usize>> = const { RefCell::new(None) };
96}
97fn set_invoke_context(new: &mut InvokeContext) {
98 INVOKE_CONTEXT.with(|invoke_context| unsafe {
99 invoke_context.replace(Some(transmute::<&mut InvokeContext, usize>(new)))
100 });
101}
102fn get_invoke_context<'a, 'b>() -> &'a mut InvokeContext<'b> {
103 let ptr = INVOKE_CONTEXT.with(|invoke_context| match *invoke_context.borrow() {
104 Some(val) => val,
105 None => panic!("Invoke context not set!"),
106 });
107 unsafe { transmute::<usize, &mut InvokeContext>(ptr) }
108}
109
110pub fn invoke_builtin_function(
111 builtin_function: solana_program_entrypoint::ProcessInstruction,
112 invoke_context: &mut InvokeContext,
113) -> Result<u64, Box<dyn std::error::Error>> {
114 set_invoke_context(invoke_context);
115
116 let transaction_context = &invoke_context.transaction_context;
117 let instruction_context = transaction_context.get_current_instruction_context()?;
118 let instruction_account_indices = 0..instruction_context.get_number_of_instruction_accounts();
119
120 invoke_context.consume_checked(1)?;
122
123 let log_collector = invoke_context.get_log_collector();
124 let program_id = instruction_context.get_last_program_key(transaction_context)?;
125 stable_log::program_invoke(
126 &log_collector,
127 program_id,
128 invoke_context.get_stack_height(),
129 );
130
131 let deduplicated_indices: HashSet<IndexOfAccount> = instruction_account_indices.collect();
133
134 let mask_out_rent_epoch_in_vm_serialization = invoke_context
136 .get_feature_set()
137 .mask_out_rent_epoch_in_vm_serialization;
138 let (mut parameter_bytes, _regions, _account_lengths) = serialize_parameters(
139 transaction_context,
140 instruction_context,
141 true, mask_out_rent_epoch_in_vm_serialization,
143 )?;
144
145 let (program_id, account_infos, input) =
147 unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) };
148
149 match std::panic::catch_unwind(AssertUnwindSafe(|| {
151 builtin_function(program_id, &account_infos, input)
152 })) {
153 Ok(program_result) => {
154 program_result.map_err(|program_error| {
155 let err = InstructionError::from(u64::from(program_error));
156 stable_log::program_failure(&log_collector, program_id, &err);
157 let err: Box<dyn std::error::Error> = Box::new(err);
158 err
159 })?;
160 }
161 Err(_panic_error) => {
162 let err = InstructionError::ProgramFailedToComplete;
163 stable_log::program_failure(&log_collector, program_id, &err);
164 let err: Box<dyn std::error::Error> = Box::new(err);
165 Err(err)?;
166 }
167 };
168
169 stable_log::program_success(&log_collector, program_id);
170
171 let account_info_map: HashMap<_, _> = account_infos.into_iter().map(|a| (a.key, a)).collect();
173
174 let transaction_context = &invoke_context.transaction_context;
177 let instruction_context = transaction_context.get_current_instruction_context()?;
178
179 for i in deduplicated_indices.into_iter() {
181 let mut borrowed_account =
182 instruction_context.try_borrow_instruction_account(transaction_context, i)?;
183 if borrowed_account.is_writable() {
184 if let Some(account_info) = account_info_map.get(borrowed_account.get_key()) {
185 if borrowed_account.get_lamports() != account_info.lamports() {
186 borrowed_account.set_lamports(account_info.lamports())?;
187 }
188
189 if borrowed_account
190 .can_data_be_resized(account_info.data_len())
191 .is_ok()
192 {
193 borrowed_account.set_data_from_slice(&account_info.data.borrow())?;
194 }
195 if borrowed_account.get_owner() != account_info.owner {
196 borrowed_account.set_owner(account_info.owner.as_ref())?;
197 }
198 }
199 }
200 }
201
202 Ok(0)
203}
204
205#[macro_export]
208macro_rules! processor {
209 ($builtin_function:expr) => {
210 Some(|vm, _arg0, _arg1, _arg2, _arg3, _arg4| {
211 let vm = unsafe {
212 &mut *((vm as *mut u64).offset(-($crate::get_runtime_environment_key() as isize))
213 as *mut $crate::EbpfVm<$crate::InvokeContext>)
214 };
215 vm.program_result =
216 $crate::invoke_builtin_function($builtin_function, vm.context_object_pointer)
217 .map_err(|err| $crate::EbpfError::SyscallError(err))
218 .into();
219 })
220 };
221}
222
223fn get_sysvar<T: Default + Sysvar + Sized + serde::de::DeserializeOwned + Clone>(
224 sysvar: Result<Arc<T>, InstructionError>,
225 var_addr: *mut u8,
226) -> u64 {
227 let invoke_context = get_invoke_context();
228 if invoke_context
229 .consume_checked(invoke_context.get_execution_cost().sysvar_base_cost + T::size_of() as u64)
230 .is_err()
231 {
232 panic!("Exceeded compute budget");
233 }
234
235 match sysvar {
236 Ok(sysvar_data) => unsafe {
237 *(var_addr as *mut _ as *mut T) = T::clone(&sysvar_data);
238 SUCCESS
239 },
240 Err(_) => UNSUPPORTED_SYSVAR,
241 }
242}
243
244struct SyscallStubs {}
245impl solana_sysvar::program_stubs::SyscallStubs for SyscallStubs {
246 fn sol_log(&self, message: &str) {
247 let invoke_context = get_invoke_context();
248 ic_msg!(invoke_context, "Program log: {}", message);
249 }
250
251 fn sol_invoke_signed(
252 &self,
253 instruction: &Instruction,
254 account_infos: &[AccountInfo],
255 signers_seeds: &[&[&[u8]]],
256 ) -> ProgramResult {
257 let instruction = StableInstruction::from(instruction.clone());
258 let invoke_context = get_invoke_context();
259 let log_collector = invoke_context.get_log_collector();
260 let transaction_context = &invoke_context.transaction_context;
261 let instruction_context = transaction_context
262 .get_current_instruction_context()
263 .unwrap();
264 let caller = instruction_context
265 .get_last_program_key(transaction_context)
266 .unwrap();
267
268 stable_log::program_invoke(
269 &log_collector,
270 &instruction.program_id,
271 invoke_context.get_stack_height(),
272 );
273
274 let signers = signers_seeds
275 .iter()
276 .map(|seeds| Pubkey::create_program_address(seeds, caller).unwrap())
277 .collect::<Vec<_>>();
278
279 let (instruction_accounts, program_indices) = invoke_context
280 .prepare_instruction(&instruction, &signers)
281 .unwrap();
282
283 let transaction_context = &invoke_context.transaction_context;
285 let instruction_context = transaction_context
286 .get_current_instruction_context()
287 .unwrap();
288 let mut account_indices = Vec::with_capacity(instruction_accounts.len());
289 for instruction_account in instruction_accounts.iter() {
290 let account_key = transaction_context
291 .get_key_of_account_at_index(instruction_account.index_in_transaction)
292 .unwrap();
293 let account_info_index = account_infos
294 .iter()
295 .position(|account_info| account_info.unsigned_key() == account_key)
296 .ok_or(InstructionError::MissingAccount)
297 .unwrap();
298 let account_info = &account_infos[account_info_index];
299 let mut borrowed_account = instruction_context
300 .try_borrow_instruction_account(
301 transaction_context,
302 instruction_account.index_in_caller,
303 )
304 .unwrap();
305 if borrowed_account.get_lamports() != account_info.lamports() {
306 borrowed_account
307 .set_lamports(account_info.lamports())
308 .unwrap();
309 }
310 let account_info_data = account_info.try_borrow_data().unwrap();
311 match borrowed_account.can_data_be_resized(account_info_data.len()) {
313 Ok(()) => borrowed_account
314 .set_data_from_slice(&account_info_data)
315 .unwrap(),
316 Err(err) if borrowed_account.get_data() != *account_info_data => {
317 panic!("{err:?}");
318 }
319 _ => {}
320 }
321 if borrowed_account.get_owner() != account_info.owner {
323 borrowed_account
324 .set_owner(account_info.owner.as_ref())
325 .unwrap();
326 }
327 if instruction_account.is_writable {
328 account_indices.push((instruction_account.index_in_caller, account_info_index));
329 }
330 }
331
332 let mut compute_units_consumed = 0;
333 invoke_context
334 .process_instruction(
335 &instruction.data,
336 &instruction_accounts,
337 &program_indices,
338 &mut compute_units_consumed,
339 &mut ExecuteTimings::default(),
340 )
341 .map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?;
342
343 let transaction_context = &invoke_context.transaction_context;
345 let instruction_context = transaction_context
346 .get_current_instruction_context()
347 .unwrap();
348 for (index_in_caller, account_info_index) in account_indices.into_iter() {
349 let borrowed_account = instruction_context
350 .try_borrow_instruction_account(transaction_context, index_in_caller)
351 .unwrap();
352 let account_info = &account_infos[account_info_index];
353 **account_info.try_borrow_mut_lamports().unwrap() = borrowed_account.get_lamports();
354 if account_info.owner != borrowed_account.get_owner() {
355 #[allow(clippy::transmute_ptr_to_ptr)]
357 #[allow(mutable_transmutes)]
358 let account_info_mut =
359 unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
360 *account_info_mut = *borrowed_account.get_owner();
361 }
362
363 let new_data = borrowed_account.get_data();
364 let new_len = new_data.len();
365
366 if account_info.data_len() != new_len {
368 account_info.realloc(new_len, true)?;
369 }
370
371 let mut data = account_info.try_borrow_mut_data()?;
373 data.clone_from_slice(new_data);
374 }
375
376 stable_log::program_success(&log_collector, &instruction.program_id);
377 Ok(())
378 }
379
380 fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 {
381 get_sysvar(
382 get_invoke_context().get_sysvar_cache().get_clock(),
383 var_addr,
384 )
385 }
386
387 fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 {
388 get_sysvar(
389 get_invoke_context().get_sysvar_cache().get_epoch_schedule(),
390 var_addr,
391 )
392 }
393
394 fn sol_get_epoch_rewards_sysvar(&self, var_addr: *mut u8) -> u64 {
395 get_sysvar(
396 get_invoke_context().get_sysvar_cache().get_epoch_rewards(),
397 var_addr,
398 )
399 }
400
401 #[allow(deprecated)]
402 fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 {
403 get_sysvar(get_invoke_context().get_sysvar_cache().get_fees(), var_addr)
404 }
405
406 fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 {
407 get_sysvar(get_invoke_context().get_sysvar_cache().get_rent(), var_addr)
408 }
409
410 fn sol_get_last_restart_slot(&self, var_addr: *mut u8) -> u64 {
411 get_sysvar(
412 get_invoke_context()
413 .get_sysvar_cache()
414 .get_last_restart_slot(),
415 var_addr,
416 )
417 }
418
419 fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
420 let (program_id, data) = get_invoke_context().transaction_context.get_return_data();
421 Some((*program_id, data.to_vec()))
422 }
423
424 fn sol_set_return_data(&self, data: &[u8]) {
425 let invoke_context = get_invoke_context();
426 let transaction_context = &mut invoke_context.transaction_context;
427 let instruction_context = transaction_context
428 .get_current_instruction_context()
429 .unwrap();
430 let caller = *instruction_context
431 .get_last_program_key(transaction_context)
432 .unwrap();
433 transaction_context
434 .set_return_data(caller, data.to_vec())
435 .unwrap();
436 }
437
438 fn sol_get_stack_height(&self) -> u64 {
439 let invoke_context = get_invoke_context();
440 invoke_context.get_stack_height().try_into().unwrap()
441 }
442}
443
444pub fn find_file(filename: &str) -> Option<PathBuf> {
445 for dir in default_shared_object_dirs() {
446 let candidate = dir.join(filename);
447 if candidate.exists() {
448 return Some(candidate);
449 }
450 }
451 None
452}
453
454fn default_shared_object_dirs() -> Vec<PathBuf> {
455 let mut search_path = vec![];
456 if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
457 search_path.push(PathBuf::from(bpf_out_dir));
458 } else if let Ok(bpf_out_dir) = std::env::var("SBF_OUT_DIR") {
459 search_path.push(PathBuf::from(bpf_out_dir));
460 }
461 search_path.push(PathBuf::from("tests/fixtures"));
462 if let Ok(dir) = std::env::current_dir() {
463 search_path.push(dir);
464 }
465 trace!("SBF .so search path: {:?}", search_path);
466 search_path
467}
468
469pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
470 let path = path.as_ref();
471 let mut file = File::open(path)
472 .unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
473
474 let mut file_data = Vec::new();
475 file.read_to_end(&mut file_data)
476 .unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", path.display(), err));
477 file_data
478}
479
480pub struct ProgramTest {
481 accounts: Vec<(Pubkey, AccountSharedData)>,
482 genesis_accounts: Vec<(Pubkey, AccountSharedData)>,
483 builtin_programs: Vec<(Pubkey, &'static str, ProgramCacheEntry)>,
484 compute_max_units: Option<u64>,
485 prefer_bpf: bool,
486 deactivate_feature_set: HashSet<Pubkey>,
487 transaction_account_lock_limit: Option<usize>,
488}
489
490impl Default for ProgramTest {
491 fn default() -> Self {
504 solana_logger::setup_with_default(
505 "solana_sbpf::vm=debug,\
506 solana_runtime::message_processor=debug,\
507 solana_runtime::system_instruction_processor=trace,\
508 solana_program_test=info",
509 );
510 let prefer_bpf =
511 std::env::var("BPF_OUT_DIR").is_ok() || std::env::var("SBF_OUT_DIR").is_ok();
512
513 Self {
514 accounts: vec![],
515 genesis_accounts: vec![],
516 builtin_programs: vec![],
517 compute_max_units: None,
518 prefer_bpf,
519 deactivate_feature_set: HashSet::default(),
520 transaction_account_lock_limit: None,
521 }
522 }
523}
524
525impl ProgramTest {
526 pub fn new(
534 program_name: &'static str,
535 program_id: Pubkey,
536 builtin_function: Option<BuiltinFunctionWithContext>,
537 ) -> Self {
538 let mut me = Self::default();
539 me.add_program(program_name, program_id, builtin_function);
540 me
541 }
542
543 pub fn prefer_bpf(&mut self, prefer_bpf: bool) {
545 self.prefer_bpf = prefer_bpf;
546 }
547
548 pub fn set_compute_max_units(&mut self, compute_max_units: u64) {
550 debug_assert!(
551 compute_max_units <= i64::MAX as u64,
552 "Compute unit limit must fit in `i64::MAX`"
553 );
554 self.compute_max_units = Some(compute_max_units);
555 }
556
557 pub fn set_transaction_account_lock_limit(&mut self, transaction_account_lock_limit: usize) {
559 self.transaction_account_lock_limit = Some(transaction_account_lock_limit);
560 }
561
562 pub fn add_genesis_account(&mut self, address: Pubkey, account: Account) {
564 self.genesis_accounts
565 .push((address, AccountSharedData::from(account)));
566 }
567
568 pub fn add_account(&mut self, address: Pubkey, account: Account) {
570 self.accounts
571 .push((address, AccountSharedData::from(account)));
572 }
573
574 pub fn add_account_with_file_data(
576 &mut self,
577 address: Pubkey,
578 lamports: u64,
579 owner: Pubkey,
580 filename: &str,
581 ) {
582 self.add_account(
583 address,
584 Account {
585 lamports,
586 data: read_file(find_file(filename).unwrap_or_else(|| {
587 panic!("Unable to locate {filename}");
588 })),
589 owner,
590 executable: false,
591 rent_epoch: 0,
592 },
593 );
594 }
595
596 pub fn add_account_with_base64_data(
599 &mut self,
600 address: Pubkey,
601 lamports: u64,
602 owner: Pubkey,
603 data_base64: &str,
604 ) {
605 self.add_account(
606 address,
607 Account {
608 lamports,
609 data: BASE64_STANDARD
610 .decode(data_base64)
611 .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
612 owner,
613 executable: false,
614 rent_epoch: 0,
615 },
616 );
617 }
618
619 pub fn add_sysvar_account<S: Sysvar>(&mut self, address: Pubkey, sysvar: &S) {
620 let account = create_account_shared_data_for_test(sysvar);
621 self.add_account(address, account.into());
622 }
623
624 pub fn add_upgradeable_program_to_genesis(
637 &mut self,
638 program_name: &'static str,
639 program_id: &Pubkey,
640 ) {
641 let program_file = find_file(&format!("{program_name}.so"))
642 .expect("Program file data not available for {program_name} ({program_id})");
643 let elf = read_file(program_file);
644 let program_accounts =
645 programs::bpf_loader_upgradeable_program_accounts(program_id, &elf, &Rent::default());
646 for (address, account) in program_accounts {
647 self.add_genesis_account(address, account);
648 }
649 }
650
651 pub fn add_program(
659 &mut self,
660 program_name: &'static str,
661 program_id: Pubkey,
662 builtin_function: Option<BuiltinFunctionWithContext>,
663 ) {
664 let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| {
665 let data = read_file(&program_file);
666 info!(
667 "\"{}\" SBF program from {}{}",
668 program_name,
669 program_file.display(),
670 std::fs::metadata(&program_file)
671 .map(|metadata| {
672 metadata
673 .modified()
674 .map(|time| {
675 format!(
676 ", modified {}",
677 HumanTime::from(time)
678 .to_text_en(Accuracy::Precise, Tense::Past)
679 )
680 })
681 .ok()
682 })
683 .ok()
684 .flatten()
685 .unwrap_or_default()
686 );
687
688 this.add_account(
689 program_id,
690 Account {
691 lamports: Rent::default().minimum_balance(data.len()).max(1),
692 data,
693 owner: solana_sdk_ids::bpf_loader::id(),
694 executable: true,
695 rent_epoch: 0,
696 },
697 );
698 };
699
700 let warn_invalid_program_name = || {
701 let valid_program_names = default_shared_object_dirs()
702 .iter()
703 .filter_map(|dir| dir.read_dir().ok())
704 .flat_map(|read_dir| {
705 read_dir.filter_map(|entry| {
706 let path = entry.ok()?.path();
707 if !path.is_file() {
708 return None;
709 }
710 match path.extension()?.to_str()? {
711 "so" => Some(path.file_stem()?.to_os_string()),
712 _ => None,
713 }
714 })
715 })
716 .collect::<Vec<_>>();
717
718 if valid_program_names.is_empty() {
719 warn!("No SBF shared objects found.");
722 return;
723 }
724
725 warn!(
726 "Possible bogus program name. Ensure the program name ({}) \
727 matches one of the following recognizable program names:",
728 program_name,
729 );
730 for name in valid_program_names {
731 warn!(" - {}", name.to_str().unwrap());
732 }
733 };
734
735 let program_file = find_file(&format!("{program_name}.so"));
736 match (self.prefer_bpf, program_file, builtin_function) {
737 (true, Some(file), _) => add_bpf(self, file),
740
741 (false, _, Some(builtin_function)) => {
744 self.add_builtin_program(program_name, program_id, builtin_function)
745 }
746
747 (true, None, _) => {
749 warn_invalid_program_name();
750 panic!("Program file data not available for {program_name} ({program_id})");
751 }
752
753 (false, _, None) => {
755 panic!("Program processor not available for {program_name} ({program_id})");
756 }
757 }
758 }
759
760 pub fn add_builtin_program(
764 &mut self,
765 program_name: &'static str,
766 program_id: Pubkey,
767 builtin_function: BuiltinFunctionWithContext,
768 ) {
769 info!("\"{}\" builtin program", program_name);
770 self.builtin_programs.push((
771 program_id,
772 program_name,
773 ProgramCacheEntry::new_builtin(0, program_name.len(), builtin_function),
774 ));
775 }
776
777 pub fn deactivate_feature(&mut self, feature_id: Pubkey) {
781 self.deactivate_feature_set.insert(feature_id);
782 }
783
784 fn setup_bank(
785 &mut self,
786 ) -> (
787 Arc<RwLock<BankForks>>,
788 Arc<RwLock<BlockCommitmentCache>>,
789 Hash,
790 GenesisConfigInfo,
791 ) {
792 {
793 use std::sync::Once;
794 static ONCE: Once = Once::new();
795
796 ONCE.call_once(|| {
797 solana_sysvar::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
798 });
799 }
800
801 let rent = Rent::default();
802 let fee_rate_governor = FeeRateGovernor {
803 lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2,
805 ..FeeRateGovernor::default()
806 };
807 let bootstrap_validator_pubkey = Pubkey::new_unique();
808 let bootstrap_validator_stake_lamports =
809 rent.minimum_balance(VoteState::size_of()) + sol_to_lamports(1_000_000.0);
810
811 let mint_keypair = Keypair::new();
812 let voting_keypair = Keypair::new();
813
814 let mut genesis_config = create_genesis_config_with_leader_ex(
815 sol_to_lamports(1_000_000.0),
816 &mint_keypair.pubkey(),
817 &bootstrap_validator_pubkey,
818 &voting_keypair.pubkey(),
819 &Pubkey::new_unique(),
820 bootstrap_validator_stake_lamports,
821 42,
822 fee_rate_governor,
823 rent.clone(),
824 ClusterType::Development,
825 std::mem::take(&mut self.genesis_accounts),
826 );
827
828 for deactivate_feature_pk in &self.deactivate_feature_set {
830 if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
831 match genesis_config.accounts.remove(deactivate_feature_pk) {
832 Some(_) => debug!("Feature for {:?} deactivated", deactivate_feature_pk),
833 None => warn!(
834 "Feature {:?} set for deactivation not found in genesis_config account list, ignored.",
835 deactivate_feature_pk
836 ),
837 }
838 } else {
839 warn!(
840 "Feature {:?} set for deactivation is not a known Feature public key",
841 deactivate_feature_pk
842 );
843 }
844 }
845
846 let target_tick_duration = Duration::from_micros(100);
847 genesis_config.poh_config = PohConfig::new_sleep(target_tick_duration);
848 debug!("Payer address: {}", mint_keypair.pubkey());
849 debug!("Genesis config: {}", genesis_config);
850
851 let bank = Bank::new_with_paths(
852 &genesis_config,
853 Arc::new(RuntimeConfig {
854 compute_budget: self.compute_max_units.map(|max_units| ComputeBudget {
855 compute_unit_limit: max_units,
856 ..ComputeBudget::default()
857 }),
858 transaction_account_lock_limit: self.transaction_account_lock_limit,
859 ..RuntimeConfig::default()
860 }),
861 Vec::default(),
862 None,
863 None,
864 false,
865 None,
866 None,
867 None,
868 Arc::default(),
869 None,
870 None,
871 );
872
873 for (program_id, account) in programs::spl_programs(&rent).iter() {
875 bank.store_account(program_id, account);
876 }
877
878 for (program_id, account) in programs::core_bpf_programs(&rent, |feature_id| {
880 genesis_config.accounts.contains_key(feature_id)
881 })
882 .iter()
883 {
884 bank.store_account(program_id, account);
885 }
886
887 let mut builtin_programs = Vec::new();
889 std::mem::swap(&mut self.builtin_programs, &mut builtin_programs);
890 for (program_id, name, builtin) in builtin_programs.into_iter() {
891 bank.add_builtin(program_id, name, builtin);
892 }
893
894 for (address, account) in self.accounts.iter() {
895 if bank.get_account(address).is_some() {
896 info!("Overriding account at {}", address);
897 }
898 bank.store_account(address, account);
899 }
900 bank.set_capitalization();
901 let bank = {
903 let bank = Arc::new(bank);
904 bank.fill_bank_with_ticks_for_tests();
905 let bank = Bank::new_from_parent(bank.clone(), bank.collector_id(), bank.slot() + 1);
906 debug!("Bank slot: {}", bank.slot());
907 bank
908 };
909 let slot = bank.slot();
910 let last_blockhash = bank.last_blockhash();
911 let bank_forks = BankForks::new_rw_arc(bank);
912 let block_commitment_cache = Arc::new(RwLock::new(
913 BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
914 ));
915
916 (
917 bank_forks,
918 block_commitment_cache,
919 last_blockhash,
920 GenesisConfigInfo {
921 genesis_config,
922 mint_keypair,
923 voting_keypair,
924 validator_pubkey: bootstrap_validator_pubkey,
925 },
926 )
927 }
928
929 pub async fn start(mut self) -> (BanksClient, Keypair, Hash) {
930 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
931 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
932 let target_slot_duration = target_tick_duration * gci.genesis_config.ticks_per_slot as u32;
933 let transport = start_local_server(
934 bank_forks.clone(),
935 block_commitment_cache.clone(),
936 target_tick_duration,
937 )
938 .await;
939 let banks_client = start_client(transport)
940 .await
941 .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
942
943 tokio::spawn(async move {
947 loop {
948 tokio::time::sleep(target_slot_duration).await;
949 bank_forks
950 .read()
951 .unwrap()
952 .working_bank()
953 .register_unique_recent_blockhash_for_test();
954 }
955 });
956
957 (banks_client, gci.mint_keypair, last_blockhash)
958 }
959
960 pub async fn start_with_context(mut self) -> ProgramTestContext {
965 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
966 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
967 let transport = start_local_server(
968 bank_forks.clone(),
969 block_commitment_cache.clone(),
970 target_tick_duration,
971 )
972 .await;
973 let banks_client = start_client(transport)
974 .await
975 .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
976
977 ProgramTestContext::new(
978 bank_forks,
979 block_commitment_cache,
980 banks_client,
981 last_blockhash,
982 gci,
983 )
984 }
985}
986
987#[async_trait]
988pub trait ProgramTestBanksClientExt {
989 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash>;
991}
992
993#[async_trait]
994impl ProgramTestBanksClientExt for BanksClient {
995 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash> {
996 let mut num_retries = 0;
997 let start = Instant::now();
998 while start.elapsed().as_secs() < 5 {
999 let new_blockhash = self.get_latest_blockhash().await?;
1000 if new_blockhash != *blockhash {
1001 return Ok(new_blockhash);
1002 }
1003 debug!("Got same blockhash ({:?}), will retry...", blockhash);
1004
1005 tokio::time::sleep(Duration::from_millis(200)).await;
1006 num_retries += 1;
1007 }
1008
1009 Err(io::Error::other(format!(
1010 "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
1011 start.elapsed().as_millis(),
1012 num_retries,
1013 blockhash
1014 )))
1015 }
1016}
1017
1018struct DroppableTask<T>(Arc<AtomicBool>, JoinHandle<T>);
1019
1020impl<T> Drop for DroppableTask<T> {
1021 fn drop(&mut self) {
1022 self.0.store(true, Ordering::Relaxed);
1023 trace!(
1024 "stopping task, which is currently {}",
1025 if self.1.is_finished() {
1026 "finished"
1027 } else {
1028 "running"
1029 }
1030 );
1031 }
1032}
1033
1034pub struct ProgramTestContext {
1035 pub banks_client: BanksClient,
1036 pub last_blockhash: Hash,
1037 pub payer: Keypair,
1038 genesis_config: GenesisConfig,
1039 bank_forks: Arc<RwLock<BankForks>>,
1040 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1041 _bank_task: DroppableTask<()>,
1042}
1043
1044impl ProgramTestContext {
1045 fn new(
1046 bank_forks: Arc<RwLock<BankForks>>,
1047 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1048 banks_client: BanksClient,
1049 last_blockhash: Hash,
1050 genesis_config_info: GenesisConfigInfo,
1051 ) -> Self {
1052 let running_bank_forks = bank_forks.clone();
1056 let target_tick_duration = genesis_config_info
1057 .genesis_config
1058 .poh_config
1059 .target_tick_duration;
1060 let target_slot_duration =
1061 target_tick_duration * genesis_config_info.genesis_config.ticks_per_slot as u32;
1062 let exit = Arc::new(AtomicBool::new(false));
1063 let bank_task = DroppableTask(
1064 exit.clone(),
1065 tokio::spawn(async move {
1066 loop {
1067 if exit.load(Ordering::Relaxed) {
1068 break;
1069 }
1070 tokio::time::sleep(target_slot_duration).await;
1071 running_bank_forks
1072 .read()
1073 .unwrap()
1074 .working_bank()
1075 .register_unique_recent_blockhash_for_test();
1076 }
1077 }),
1078 );
1079
1080 Self {
1081 banks_client,
1082 last_blockhash,
1083 payer: genesis_config_info.mint_keypair,
1084 genesis_config: genesis_config_info.genesis_config,
1085 bank_forks,
1086 block_commitment_cache,
1087 _bank_task: bank_task,
1088 }
1089 }
1090
1091 pub fn genesis_config(&self) -> &GenesisConfig {
1092 &self.genesis_config
1093 }
1094
1095 pub fn increment_vote_account_credits(
1097 &mut self,
1098 vote_account_address: &Pubkey,
1099 number_of_credits: u64,
1100 ) {
1101 let bank_forks = self.bank_forks.read().unwrap();
1102 let bank = bank_forks.working_bank();
1103
1104 let mut vote_account = bank.get_account(vote_account_address).unwrap();
1106 let mut vote_state = vote_state::from(&vote_account).unwrap();
1107
1108 let epoch = bank.epoch();
1109 for _ in 0..number_of_credits {
1110 vote_state.increment_credits(epoch, 1);
1111 }
1112 let versioned = VoteStateVersions::new_current(vote_state);
1113 vote_state::to(&versioned, &mut vote_account).unwrap();
1114 bank.store_account(vote_account_address, &vote_account);
1115 }
1116
1117 pub fn set_account(&mut self, address: &Pubkey, account: &AccountSharedData) {
1124 let bank_forks = self.bank_forks.read().unwrap();
1125 let bank = bank_forks.working_bank();
1126 bank.store_account(address, account);
1127 }
1128
1129 pub fn set_sysvar<T: SysvarId + Sysvar>(&self, sysvar: &T) {
1136 let bank_forks = self.bank_forks.read().unwrap();
1137 let bank = bank_forks.working_bank();
1138 bank.set_sysvar_for_tests(sysvar);
1139 }
1140
1141 pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
1143 let mut bank_forks = self.bank_forks.write().unwrap();
1144 let bank = bank_forks.working_bank();
1145
1146 bank.fill_bank_with_ticks_for_tests();
1149
1150 let working_slot = bank.slot();
1152 if warp_slot <= working_slot {
1153 return Err(ProgramTestError::InvalidWarpSlot);
1154 }
1155
1156 let pre_warp_slot = warp_slot - 1;
1160 let warp_bank = if pre_warp_slot == working_slot {
1161 bank.freeze();
1162 bank
1163 } else {
1164 bank_forks
1165 .insert(Bank::warp_from_parent(
1166 bank,
1167 &Pubkey::default(),
1168 pre_warp_slot,
1169 solana_accounts_db::accounts_db::CalcAccountsHashDataSource::IndexForTests,
1171 ))
1172 .clone_without_scheduler()
1173 };
1174
1175 let (snapshot_request_sender, snapshot_request_receiver) = crossbeam_channel::unbounded();
1176 let snapshot_controller = SnapshotController::new(
1177 snapshot_request_sender,
1178 SnapshotConfig::new_disabled(),
1179 bank_forks.root(),
1180 );
1181
1182 bank_forks
1183 .set_root(
1184 pre_warp_slot,
1185 Some(&snapshot_controller),
1186 Some(pre_warp_slot),
1187 )
1188 .unwrap();
1189
1190 snapshot_request_receiver
1194 .try_iter()
1195 .filter(|snapshot_request| {
1196 snapshot_request.request_kind == SnapshotRequestKind::EpochAccountsHash
1197 })
1198 .for_each(|snapshot_request| {
1199 snapshot_request
1200 .snapshot_root_bank
1201 .rc
1202 .accounts
1203 .accounts_db
1204 .epoch_accounts_hash_manager
1205 .set_valid(
1206 EpochAccountsHash::new(Hash::new_unique()),
1207 snapshot_request.snapshot_root_bank.slot(),
1208 )
1209 });
1210
1211 bank_forks.insert(Bank::new_from_parent(
1213 warp_bank,
1214 &Pubkey::default(),
1215 warp_slot,
1216 ));
1217
1218 let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1221 w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1226
1227 let bank = bank_forks.working_bank();
1228 self.last_blockhash = bank.last_blockhash();
1229 Ok(())
1230 }
1231
1232 pub fn warp_to_epoch(&mut self, warp_epoch: Epoch) -> Result<(), ProgramTestError> {
1233 let warp_slot = self
1234 .genesis_config
1235 .epoch_schedule
1236 .get_first_slot_in_epoch(warp_epoch);
1237 self.warp_to_slot(warp_slot)
1238 }
1239
1240 pub fn warp_forward_force_reward_interval_end(&mut self) -> Result<(), ProgramTestError> {
1242 let mut bank_forks = self.bank_forks.write().unwrap();
1243 let bank = bank_forks.working_bank();
1244
1245 bank.fill_bank_with_ticks_for_tests();
1248 let pre_warp_slot = bank.slot();
1249
1250 bank_forks
1251 .set_root(
1252 pre_warp_slot,
1253 None, Some(pre_warp_slot),
1255 )
1256 .unwrap();
1257
1258 let warp_slot = pre_warp_slot + 1;
1260 let mut warp_bank = Bank::new_from_parent(bank, &Pubkey::default(), warp_slot);
1261
1262 warp_bank.force_reward_interval_end_for_tests();
1263 bank_forks.insert(warp_bank);
1264
1265 let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1268 w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1273
1274 let bank = bank_forks.working_bank();
1275 self.last_blockhash = bank.last_blockhash();
1276 Ok(())
1277 }
1278
1279 pub async fn get_new_latest_blockhash(&mut self) -> io::Result<Hash> {
1281 let blockhash = self
1282 .banks_client
1283 .get_new_latest_blockhash(&self.last_blockhash)
1284 .await?;
1285 self.last_blockhash = blockhash;
1286 Ok(blockhash)
1287 }
1288
1289 pub fn register_hard_fork(&mut self, hard_fork_slot: Slot) {
1291 self.bank_forks
1292 .read()
1293 .unwrap()
1294 .working_bank()
1295 .register_hard_fork(hard_fork_slot)
1296 }
1297}