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