1#![allow(clippy::arithmetic_side_effects)]
2use {
3 agave_feature_set::{
4 FEATURE_NAMES, FeatureSet, alpenglow, increase_cpi_account_info_limit,
5 raise_cpi_nesting_limit_to_8,
6 },
7 agave_snapshots::{
8 SnapshotInterval, paths::BANK_SNAPSHOTS_DIR, snapshot_config::SnapshotConfig,
9 },
10 agave_syscalls::create_program_runtime_environment_v1,
11 base64::{Engine, prelude::BASE64_STANDARD},
12 crossbeam_channel::Receiver,
13 log::*,
14 solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
15 solana_accounts_db::{
16 accounts_db::AccountsDbConfig, accounts_index::AccountsIndexConfig,
17 utils::create_accounts_run_and_snapshot_dirs,
18 },
19 solana_cli_output::CliAccount,
20 solana_clock::{DEFAULT_MS_PER_SLOT, Slot},
21 solana_commitment_config::CommitmentConfig,
22 solana_compute_budget::compute_budget::ComputeBudget,
23 solana_core::{
24 admin_rpc_post_init::AdminRpcRequestMetadataPostInit,
25 consensus::tower_storage::TowerStorage,
26 validator::{Validator, ValidatorConfig, ValidatorStartProgress, ValidatorTpuConfig},
27 },
28 solana_epoch_schedule::EpochSchedule,
29 solana_fee_calculator::FeeRateGovernor,
30 solana_genesis_utils::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
31 solana_geyser_plugin_manager::{
32 GeyserPluginManagerRequest, geyser_plugin_manager::GeyserPluginManager,
33 },
34 solana_gossip::{
35 cluster_info::{ClusterInfo, NodeConfig},
36 contact_info::Protocol,
37 node::Node,
38 },
39 solana_inflation::Inflation,
40 solana_instruction::{AccountMeta, Instruction},
41 solana_keypair::{Keypair, read_keypair_file, write_keypair_file},
42 solana_ledger::{
43 blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions,
44 create_new_tmp_ledger,
45 },
46 solana_loader_v3_interface::state::UpgradeableLoaderState,
47 solana_message::Message,
48 solana_native_token::LAMPORTS_PER_SOL,
49 solana_net_utils::{
50 PortRange, SocketAddrSpace, find_available_ports_in_range, multihomed_sockets::BindIpAddrs,
51 },
52 solana_program_runtime::{
53 execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
54 },
55 solana_pubkey::Pubkey,
56 solana_rent::Rent,
57 solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig},
58 solana_rpc_client::{nonblocking, rpc_client::RpcClient},
59 solana_rpc_client_api::{
60 client_error::Error as RpcClientError, request::MAX_MULTIPLE_ACCOUNTS,
61 },
62 solana_runtime::{
63 bank_forks::BankForks, genesis_utils::create_genesis_config_with_leader_ex,
64 runtime_config::RuntimeConfig,
65 },
66 solana_sbpf::{elf::Executable, verifier::RequisiteVerifier},
67 solana_sdk_ids::address_lookup_table,
68 solana_signer::Signer,
69 solana_streamer::quic::DEFAULT_QUIC_ENDPOINTS,
70 solana_transaction::{Transaction, TransactionError},
71 solana_validator_exit::Exit,
72 std::{
73 collections::{HashMap, HashSet},
74 ffi::OsStr,
75 fmt::Display,
76 fs::{self, File, remove_dir_all},
77 io::Read,
78 net::{IpAddr, Ipv4Addr, SocketAddr},
79 num::{NonZero, NonZeroU64},
80 path::{Path, PathBuf},
81 str::FromStr,
82 sync::{Arc, RwLock},
83 time::Duration,
84 },
85 tokio::time::sleep,
86};
87
88#[derive(Clone)]
89pub struct AccountInfo<'a> {
90 pub address: Option<Pubkey>,
91 pub filename: &'a str,
92}
93
94#[derive(Clone)]
95pub struct UpgradeableProgramInfo {
96 pub program_id: Pubkey,
97 pub loader: Pubkey,
98 pub upgrade_authority: Pubkey,
99 pub program_path: PathBuf,
100}
101
102#[derive(Debug)]
103pub struct TestValidatorNodeConfig {
104 gossip_addr: SocketAddr,
105 port_range: PortRange,
106 bind_ip_addr: IpAddr,
107}
108
109impl Default for TestValidatorNodeConfig {
110 fn default() -> Self {
111 let bind_ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
112 #[cfg(not(debug_assertions))]
113 let port_range = solana_net_utils::VALIDATOR_PORT_RANGE;
114 #[cfg(debug_assertions)]
115 let port_range = solana_net_utils::sockets::localhost_port_range_for_tests();
116 Self {
117 gossip_addr: SocketAddr::new(bind_ip_addr, port_range.0),
118 port_range,
119 bind_ip_addr,
120 }
121 }
122}
123
124pub struct TestValidatorGenesis {
125 fee_rate_governor: FeeRateGovernor,
126 ledger_path: Option<PathBuf>,
127 tower_storage: Option<Arc<dyn TowerStorage>>,
128 pub rent: Rent,
129 rpc_config: JsonRpcConfig,
130 pubsub_config: PubSubConfig,
131 rpc_ports: Option<(u16, u16)>, warp_slot: Option<Slot>,
133 accounts: HashMap<Pubkey, AccountSharedData>,
134 upgradeable_programs: Vec<UpgradeableProgramInfo>,
135 ticks_per_slot: Option<u64>,
136 epoch_schedule: Option<EpochSchedule>,
137 inflation: Option<Inflation>,
138 node_config: TestValidatorNodeConfig,
139 pub validator_exit: Arc<RwLock<Exit>>,
140 pub start_progress: Arc<RwLock<ValidatorStartProgress>>,
141 pub authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
142 pub staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
143 pub max_ledger_shreds: Option<u64>,
144 pub max_genesis_archive_unpacked_size: Option<u64>,
145 pub geyser_plugin_config_files: Option<Vec<PathBuf>>,
146 pub enable_scheduler_bindings: bool,
147 deactivate_feature_set: HashSet<Pubkey>,
148 compute_unit_limit: Option<u64>,
149 pub log_messages_bytes_limit: Option<usize>,
150 pub transaction_account_lock_limit: Option<usize>,
151 pub geyser_plugin_manager: Arc<RwLock<GeyserPluginManager>>,
152 admin_rpc_service_post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
153}
154
155impl Default for TestValidatorGenesis {
156 fn default() -> Self {
157 let deactivate_feature_set = [alpenglow::id()].into_iter().collect();
159 Self {
160 fee_rate_governor: FeeRateGovernor::default(),
161 ledger_path: Option::<PathBuf>::default(),
162 tower_storage: Option::<Arc<dyn TowerStorage>>::default(),
163 rent: Rent::default(),
164 rpc_config: JsonRpcConfig::default_for_test(),
165 pubsub_config: PubSubConfig::default(),
166 rpc_ports: Option::<(u16, u16)>::default(),
167 warp_slot: Option::<Slot>::default(),
168 accounts: HashMap::<Pubkey, AccountSharedData>::default(),
169 upgradeable_programs: Vec::<UpgradeableProgramInfo>::default(),
170 ticks_per_slot: Option::<u64>::default(),
171 epoch_schedule: Option::<EpochSchedule>::default(),
172 inflation: Option::<Inflation>::default(),
173 node_config: TestValidatorNodeConfig::default(),
174 validator_exit: Arc::<RwLock<Exit>>::default(),
175 start_progress: Arc::<RwLock<ValidatorStartProgress>>::default(),
176 authorized_voter_keypairs: Arc::<RwLock<Vec<Arc<Keypair>>>>::default(),
177 staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
178 max_ledger_shreds: Option::<u64>::default(),
179 max_genesis_archive_unpacked_size: Option::<u64>::default(),
180 geyser_plugin_config_files: Option::<Vec<PathBuf>>::default(),
181 enable_scheduler_bindings: false,
182 deactivate_feature_set,
183 compute_unit_limit: Option::<u64>::default(),
184 log_messages_bytes_limit: Option::<usize>::default(),
185 transaction_account_lock_limit: Option::<usize>::default(),
186 geyser_plugin_manager: Arc::new(RwLock::new(GeyserPluginManager::default())),
187 admin_rpc_service_post_init:
188 Arc::<RwLock<Option<AdminRpcRequestMetadataPostInit>>>::default(),
189 }
190 }
191}
192
193fn try_transform_program_data(
194 address: &Pubkey,
195 account: &mut AccountSharedData,
196) -> Result<(), String> {
197 if account.owner() == &solana_sdk_ids::bpf_loader_upgradeable::id() {
198 let programdata_offset = UpgradeableLoaderState::size_of_programdata_metadata();
199 let programdata_meta = account.data().get(0..programdata_offset).ok_or(format!(
200 "Failed to get upgradeable programdata data from {address}"
201 ))?;
202 if let Ok(UpgradeableLoaderState::ProgramData {
205 upgrade_authority_address,
206 ..
207 }) = bincode::deserialize::<UpgradeableLoaderState>(programdata_meta)
208 {
209 bincode::serialize_into(
212 account.data_as_mut_slice(),
213 &UpgradeableLoaderState::ProgramData {
214 slot: 0,
215 upgrade_authority_address,
216 },
217 )
218 .map_err(|_| format!("Failed to write to upgradeable programdata account {address}"))
219 } else {
220 Err(format!(
221 "Failed to read upgradeable programdata account {address}"
222 ))
223 }
224 } else {
225 Err(format!("Account {address} not owned by upgradeable loader"))
226 }
227}
228
229impl TestValidatorGenesis {
230 pub fn deactivate_features(&mut self, deactivate_list: &[Pubkey]) -> &mut Self {
234 self.deactivate_feature_set.extend(deactivate_list);
235 self
236 }
237 pub fn ledger_path<P: Into<PathBuf>>(&mut self, ledger_path: P) -> &mut Self {
238 self.ledger_path = Some(ledger_path.into());
239 self
240 }
241
242 pub fn tower_storage(&mut self, tower_storage: Arc<dyn TowerStorage>) -> &mut Self {
243 self.tower_storage = Some(tower_storage);
244 self
245 }
246
247 pub fn ledger_exists(ledger_path: &Path) -> bool {
249 ledger_path.join("vote-account-keypair.json").exists()
250 }
251
252 pub fn fee_rate_governor(&mut self, fee_rate_governor: FeeRateGovernor) -> &mut Self {
253 self.fee_rate_governor = fee_rate_governor;
254 self
255 }
256
257 pub fn ticks_per_slot(&mut self, ticks_per_slot: u64) -> &mut Self {
258 self.ticks_per_slot = Some(ticks_per_slot);
259 self
260 }
261
262 pub fn epoch_schedule(&mut self, epoch_schedule: EpochSchedule) -> &mut Self {
263 self.epoch_schedule = Some(epoch_schedule);
264 self
265 }
266
267 pub fn inflation(&mut self, inflation: Inflation) -> &mut Self {
268 self.inflation = Some(inflation);
269 self
270 }
271
272 pub fn rent(&mut self, rent: Rent) -> &mut Self {
273 self.rent = rent;
274 self
275 }
276
277 pub fn rpc_config(&mut self, rpc_config: JsonRpcConfig) -> &mut Self {
278 self.rpc_config = rpc_config;
279 self
280 }
281
282 pub fn pubsub_config(&mut self, pubsub_config: PubSubConfig) -> &mut Self {
283 self.pubsub_config = pubsub_config;
284 self
285 }
286
287 pub fn rpc_port(&mut self, rpc_port: u16) -> &mut Self {
288 self.rpc_ports = Some((rpc_port, rpc_port + 1));
289 self
290 }
291
292 pub fn faucet_addr(&mut self, faucet_addr: Option<SocketAddr>) -> &mut Self {
293 self.rpc_config.faucet_addr = faucet_addr;
294 self
295 }
296
297 pub fn warp_slot(&mut self, warp_slot: Slot) -> &mut Self {
298 self.warp_slot = Some(warp_slot);
299 self
300 }
301
302 pub fn gossip_host(&mut self, gossip_host: IpAddr) -> &mut Self {
303 self.node_config.gossip_addr.set_ip(gossip_host);
304 self
305 }
306
307 pub fn gossip_port(&mut self, gossip_port: u16) -> &mut Self {
308 self.node_config.gossip_addr.set_port(gossip_port);
309 self
310 }
311
312 pub fn port_range(&mut self, port_range: PortRange) -> &mut Self {
313 self.node_config.port_range = port_range;
314 self
315 }
316
317 pub fn bind_ip_addr(&mut self, bind_ip_addr: IpAddr) -> &mut Self {
318 self.node_config.bind_ip_addr = bind_ip_addr;
319 self
320 }
321
322 pub fn compute_unit_limit(&mut self, compute_unit_limit: u64) -> &mut Self {
323 self.compute_unit_limit = Some(compute_unit_limit);
324 self
325 }
326
327 pub fn add_account(&mut self, address: Pubkey, account: AccountSharedData) -> &mut Self {
329 self.accounts.insert(address, account);
330 self
331 }
332
333 pub fn add_accounts<T>(&mut self, accounts: T) -> &mut Self
334 where
335 T: IntoIterator<Item = (Pubkey, AccountSharedData)>,
336 {
337 for (address, account) in accounts {
338 self.add_account(address, account);
339 }
340 self
341 }
342
343 fn clone_accounts_and_transform<T, F>(
344 &mut self,
345 addresses: T,
346 rpc_client: &RpcClient,
347 skip_missing: bool,
348 transform: F,
349 ) -> Result<&mut Self, String>
350 where
351 T: IntoIterator<Item = Pubkey>,
352 F: Fn(&Pubkey, Account) -> Result<AccountSharedData, String>,
353 {
354 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
355 for chunk in addresses.chunks(MAX_MULTIPLE_ACCOUNTS) {
356 info!("Fetching {chunk:?} over RPC...");
357 let responses = rpc_client
358 .get_multiple_accounts(chunk)
359 .map_err(|err| format!("Failed to fetch: {err}"))?;
360 for (address, res) in chunk.iter().zip(responses) {
361 if let Some(account) = res {
362 self.add_account(*address, transform(address, account)?);
363 } else if skip_missing {
364 warn!("Could not find {address}, skipping.");
365 } else {
366 return Err(format!("Failed to fetch {address}"));
367 }
368 }
369 }
370 Ok(self)
371 }
372
373 pub fn clone_accounts<T>(
374 &mut self,
375 addresses: T,
376 rpc_client: &RpcClient,
377 skip_missing: bool,
378 ) -> Result<&mut Self, String>
379 where
380 T: IntoIterator<Item = Pubkey>,
381 {
382 self.clone_accounts_and_transform(
383 addresses,
384 rpc_client,
385 skip_missing,
386 |address, account| {
387 let mut account_shared_data = AccountSharedData::from(account);
388 try_transform_program_data(address, &mut account_shared_data).ok();
390 Ok(account_shared_data)
391 },
392 )
393 }
394
395 pub fn deep_clone_address_lookup_table_accounts<T>(
396 &mut self,
397 addresses: T,
398 rpc_client: &RpcClient,
399 ) -> Result<&mut Self, String>
400 where
401 T: IntoIterator<Item = Pubkey>,
402 {
403 const LOOKUP_TABLE_META_SIZE: usize = 56;
404 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
405 let mut alt_entries: Vec<Pubkey> = Vec::new();
406
407 for chunk in addresses.chunks(MAX_MULTIPLE_ACCOUNTS) {
408 info!("Fetching {chunk:?} over RPC...");
409 let responses = rpc_client
410 .get_multiple_accounts(chunk)
411 .map_err(|err| format!("Failed to fetch: {err}"))?;
412 for (address, res) in chunk.iter().zip(responses) {
413 if let Some(account) = res {
414 if address_lookup_table::check_id(account.owner()) {
415 let raw_addresses_data = account
416 .data()
417 .get(LOOKUP_TABLE_META_SIZE..)
418 .ok_or(format!("Failed to get addresses data from {address}"))?;
419
420 if raw_addresses_data.len() % std::mem::size_of::<Pubkey>() != 0 {
421 return Err(format!("Invalid alt account data length for {address}"));
422 }
423
424 for address_slice in
425 raw_addresses_data.chunks_exact(std::mem::size_of::<Pubkey>())
426 {
427 let address = Pubkey::try_from(address_slice).unwrap();
429 alt_entries.push(address);
430 }
431 self.add_account(*address, AccountSharedData::from(account));
432 } else {
433 return Err(format!("Account {address} is not an address lookup table"));
434 }
435 } else {
436 return Err(format!("Failed to fetch {address}"));
437 }
438 }
439 }
440
441 self.clone_accounts(alt_entries, rpc_client, true)
442 }
443
444 pub fn clone_programdata_accounts<T>(
445 &mut self,
446 addresses: T,
447 rpc_client: &RpcClient,
448 skip_missing: bool,
449 ) -> Result<&mut Self, String>
450 where
451 T: IntoIterator<Item = Pubkey>,
452 {
453 self.clone_accounts_and_transform(
454 addresses,
455 rpc_client,
456 skip_missing,
457 |address, account| {
458 let mut account_shared_data = AccountSharedData::from(account);
459 try_transform_program_data(address, &mut account_shared_data)?;
460 Ok(account_shared_data)
461 },
462 )
463 }
464
465 pub fn clone_upgradeable_programs<T>(
466 &mut self,
467 addresses: T,
468 rpc_client: &RpcClient,
469 ) -> Result<&mut Self, String>
470 where
471 T: IntoIterator<Item = Pubkey>,
472 {
473 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
474 self.clone_accounts(addresses.clone(), rpc_client, false)?;
475
476 let mut programdata_addresses: HashSet<Pubkey> = HashSet::new();
477 for address in addresses {
478 let account = self.accounts.get(&address).unwrap();
479
480 if let Ok(UpgradeableLoaderState::Program {
481 programdata_address,
482 }) = account.deserialize_data()
483 {
484 programdata_addresses.insert(programdata_address);
485 } else {
486 return Err(format!(
487 "Failed to read upgradeable program account {address}",
488 ));
489 }
490 }
491
492 self.clone_programdata_accounts(programdata_addresses, rpc_client, false)?;
493
494 Ok(self)
495 }
496
497 pub fn clone_feature_set(&mut self, rpc_client: &RpcClient) -> Result<&mut Self, String> {
498 for feature_ids in FEATURE_NAMES
499 .keys()
500 .cloned()
501 .collect::<Vec<Pubkey>>()
502 .chunks(MAX_MULTIPLE_ACCOUNTS)
503 {
504 rpc_client
505 .get_multiple_accounts(feature_ids)
506 .map_err(|err| format!("Failed to fetch: {err}"))?
507 .into_iter()
508 .zip(feature_ids)
509 .for_each(|(maybe_account, feature_id)| {
510 if maybe_account
511 .as_ref()
512 .and_then(solana_feature_gate_interface::from_account)
513 .and_then(|feature| feature.activated_at)
514 .is_none()
515 {
516 self.deactivate_feature_set.insert(*feature_id);
517 }
518 });
519 }
520 Ok(self)
521 }
522
523 pub fn add_accounts_from_json_files(
524 &mut self,
525 accounts: &[AccountInfo],
526 ) -> Result<&mut Self, String> {
527 for account in accounts {
528 let Some(account_path) = solana_program_test::find_file(account.filename) else {
529 return Err(format!("Unable to locate {}", account.filename));
530 };
531 let mut file = File::open(&account_path).unwrap();
532 let mut account_info_raw = String::new();
533 file.read_to_string(&mut account_info_raw).unwrap();
534
535 let result: serde_json::Result<CliAccount> = serde_json::from_str(&account_info_raw);
536 let account_info = match result {
537 Err(err) => {
538 return Err(format!(
539 "Unable to deserialize {}: {}",
540 account_path.to_str().unwrap(),
541 err
542 ));
543 }
544 Ok(deserialized) => deserialized,
545 };
546
547 let address = account.address.unwrap_or_else(|| {
548 Pubkey::from_str(account_info.keyed_account.pubkey.as_str()).unwrap()
549 });
550 let account = account_info
551 .keyed_account
552 .account
553 .to_account_shared_data()
554 .unwrap();
555
556 self.add_account(address, account);
557 }
558 Ok(self)
559 }
560
561 pub fn add_accounts_from_directories<T, P>(&mut self, dirs: T) -> Result<&mut Self, String>
562 where
563 T: IntoIterator<Item = P>,
564 P: AsRef<Path> + Display,
565 {
566 let mut json_files: HashSet<String> = HashSet::new();
567 for dir in dirs {
568 let matched_files = match fs::read_dir(&dir) {
569 Ok(dir) => dir,
570 Err(e) => return Err(format!("Cannot read directory {}: {}", &dir, e)),
571 }
572 .flatten()
573 .map(|entry| entry.path())
574 .filter(|path| path.is_file() && path.extension() == Some(OsStr::new("json")))
575 .map(|path| String::from(path.to_string_lossy()));
576
577 json_files.extend(matched_files);
578 }
579
580 debug!("account files found: {json_files:?}");
581
582 let accounts: Vec<_> = json_files
583 .iter()
584 .map(|filename| AccountInfo {
585 address: None,
586 filename,
587 })
588 .collect();
589
590 self.add_accounts_from_json_files(&accounts)?;
591
592 Ok(self)
593 }
594
595 pub fn add_account_with_file_data(
597 &mut self,
598 address: Pubkey,
599 lamports: u64,
600 owner: Pubkey,
601 filename: &str,
602 ) -> &mut Self {
603 self.add_account(
604 address,
605 AccountSharedData::from(Account {
606 lamports,
607 data: solana_program_test::read_file(
608 solana_program_test::find_file(filename).unwrap_or_else(|| {
609 panic!("Unable to locate {filename}");
610 }),
611 ),
612 owner,
613 executable: false,
614 rent_epoch: 0,
615 }),
616 )
617 }
618
619 pub fn add_account_with_base64_data(
622 &mut self,
623 address: Pubkey,
624 lamports: u64,
625 owner: Pubkey,
626 data_base64: &str,
627 ) -> &mut Self {
628 self.add_account(
629 address,
630 AccountSharedData::from(Account {
631 lamports,
632 data: BASE64_STANDARD
633 .decode(data_base64)
634 .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
635 owner,
636 executable: false,
637 rent_epoch: 0,
638 }),
639 )
640 }
641
642 pub fn add_program(&mut self, program_name: &str, program_id: Pubkey) -> &mut Self {
647 let program_path = solana_program_test::find_file(&format!("{program_name}.so"))
648 .unwrap_or_else(|| panic!("Unable to locate program {program_name}"));
649
650 self.upgradeable_programs.push(UpgradeableProgramInfo {
651 program_id,
652 loader: solana_sdk_ids::bpf_loader_upgradeable::id(),
653 upgrade_authority: Pubkey::default(),
654 program_path,
655 });
656 self
657 }
658
659 pub fn add_upgradeable_programs_with_path(
661 &mut self,
662 programs: &[UpgradeableProgramInfo],
663 ) -> &mut Self {
664 for program in programs {
665 self.upgradeable_programs.push(program.clone());
666 }
667 self
668 }
669
670 pub fn start_with_mint_address(
675 &self,
676 mint_address: Pubkey,
677 socket_addr_space: SocketAddrSpace,
678 ) -> Result<TestValidator, Box<dyn std::error::Error>> {
679 self.start_with_mint_address_and_geyser_plugin_rpc(mint_address, socket_addr_space, None)
680 }
681
682 pub fn start_with_mint_address_and_geyser_plugin_rpc(
688 &self,
689 mint_address: Pubkey,
690 socket_addr_space: SocketAddrSpace,
691 rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
692 ) -> Result<TestValidator, Box<dyn std::error::Error>> {
693 TestValidator::start(
694 mint_address,
695 self,
696 socket_addr_space,
697 rpc_to_plugin_manager_receiver,
698 )
699 .inspect(|test_validator| {
700 let runtime = tokio::runtime::Builder::new_current_thread()
701 .enable_io()
702 .enable_time()
703 .build()
704 .unwrap();
705 runtime.block_on(test_validator.wait_for_nonzero_fees());
706 })
707 }
708
709 pub fn start(&self) -> (TestValidator, Keypair) {
716 self.start_with_socket_addr_space(SocketAddrSpace::new(true))
717 }
718
719 pub fn start_with_socket_addr_space(
727 &self,
728 socket_addr_space: SocketAddrSpace,
729 ) -> (TestValidator, Keypair) {
730 let mint_keypair = Keypair::new();
731 self.start_with_mint_address(mint_keypair.pubkey(), socket_addr_space)
732 .inspect(|test_validator| {
733 let runtime = tokio::runtime::Builder::new_current_thread()
734 .enable_io()
735 .enable_time()
736 .build()
737 .unwrap();
738 let upgradeable_program_ids: Vec<&Pubkey> = self
739 .upgradeable_programs
740 .iter()
741 .map(|p| &p.program_id)
742 .collect();
743 runtime
744 .block_on(test_validator.wait_for_upgradeable_programs_deployed(
745 &upgradeable_program_ids,
746 &mint_keypair,
747 ))
748 .unwrap_or_else(|err| {
749 panic!("Failed to wait for programs to be deployed: {err:?}")
750 });
751 })
752 .map(|test_validator| (test_validator, mint_keypair))
753 .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"))
754 }
755
756 pub async fn start_async_with_mint_address(
759 &self,
760 mint_keypair: &Keypair,
761 socket_addr_space: SocketAddrSpace,
762 ) -> Result<TestValidator, Box<dyn std::error::Error>> {
763 let test_validator =
764 TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space, None)?;
765 test_validator.wait_for_nonzero_fees().await;
766 let upgradeable_program_ids: Vec<&Pubkey> = self
767 .upgradeable_programs
768 .iter()
769 .map(|p| &p.program_id)
770 .collect();
771 test_validator
772 .wait_for_upgradeable_programs_deployed(&upgradeable_program_ids, mint_keypair)
773 .await
774 .unwrap_or_else(|err| panic!("Failed to wait for programs to be deployed: {err:?}"));
775 Ok(test_validator)
776 }
777
778 pub async fn start_async(&self) -> (TestValidator, Keypair) {
779 self.start_async_with_socket_addr_space(SocketAddrSpace::new(
780 true,
781 ))
782 .await
783 }
784
785 pub async fn start_async_with_socket_addr_space(
786 &self,
787 socket_addr_space: SocketAddrSpace,
788 ) -> (TestValidator, Keypair) {
789 let mint_keypair = Keypair::new();
790 let test_validator = self
791 .start_async_with_mint_address(&mint_keypair, socket_addr_space)
792 .await
793 .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"));
794 (test_validator, mint_keypair)
795 }
796}
797
798pub struct TestValidator {
799 ledger_path: PathBuf,
800 preserve_ledger: bool,
801 rpc_pubsub_url: String,
802 rpc_url: String,
803 tpu_quic: SocketAddr,
804 gossip: SocketAddr,
805 validator: Option<Validator>,
806 vote_account_address: Pubkey,
807}
808
809impl TestValidator {
810 fn start_with_config(
813 mint_address: Pubkey,
814 faucet_addr: Option<SocketAddr>,
815 socket_addr_space: SocketAddrSpace,
816 target_lamports_per_signature: u64,
817 wait_for_fees: bool,
818 ) -> Self {
819 let test_validator = TestValidatorGenesis::default()
820 .fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
821 .rent(Rent {
822 lamports_per_byte_year: 1,
823 exemption_threshold: 1.0,
824 ..Rent::default()
825 })
826 .faucet_addr(faucet_addr)
827 .start_with_mint_address(mint_address, socket_addr_space)
828 .expect("validator start failed");
829
830 if wait_for_fees {
831 let runtime = tokio::runtime::Builder::new_current_thread()
832 .enable_io()
833 .enable_time()
834 .build()
835 .unwrap();
836 runtime.block_on(test_validator.wait_for_nonzero_fees());
837 }
838 test_validator
839 }
840
841 async fn async_start_with_config(
843 mint_keypair: &Keypair,
844 faucet_addr: Option<SocketAddr>,
845 socket_addr_space: SocketAddrSpace,
846 target_lamports_per_signature: u64,
847 ) -> Self {
848 TestValidatorGenesis::default()
849 .fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
850 .rent(Rent {
851 lamports_per_byte_year: 1,
852 exemption_threshold: 1.0,
853 ..Rent::default()
854 })
855 .faucet_addr(faucet_addr)
856 .start_async_with_mint_address(mint_keypair, socket_addr_space)
857 .await
858 .expect("validator start failed")
859 }
860
861 pub fn with_no_fees(
866 mint_address: Pubkey,
867 faucet_addr: Option<SocketAddr>,
868 socket_addr_space: SocketAddrSpace,
869 ) -> Self {
870 Self::start_with_config(mint_address, faucet_addr, socket_addr_space, 0, false)
871 }
872
873 pub fn with_custom_fees(
878 mint_address: Pubkey,
879 target_lamports_per_signature: u64,
880 faucet_addr: Option<SocketAddr>,
881 socket_addr_space: SocketAddrSpace,
882 ) -> Self {
883 Self::start_with_config(
884 mint_address,
885 faucet_addr,
886 socket_addr_space,
887 target_lamports_per_signature,
888 true,
889 )
890 }
891
892 pub async fn async_with_no_fees(
897 mint_keypair: &Keypair,
898 faucet_addr: Option<SocketAddr>,
899 socket_addr_space: SocketAddrSpace,
900 ) -> Self {
901 Self::async_start_with_config(mint_keypair, faucet_addr, socket_addr_space, 0).await
902 }
903
904 pub async fn async_with_custom_fees(
909 mint_keypair: &Keypair,
910 target_lamports_per_signature: u64,
911 faucet_addr: Option<SocketAddr>,
912 socket_addr_space: SocketAddrSpace,
913 ) -> Self {
914 Self::async_start_with_config(
915 mint_keypair,
916 faucet_addr,
917 socket_addr_space,
918 target_lamports_per_signature,
919 )
920 .await
921 }
922
923 fn initialize_ledger(
930 mint_address: Pubkey,
931 config: &TestValidatorGenesis,
932 ) -> Result<PathBuf, Box<dyn std::error::Error>> {
933 let validator_identity = Keypair::new();
934 let validator_vote_account = Keypair::new();
935 let validator_stake_account = Keypair::new();
936 let validator_identity_lamports = 500 * LAMPORTS_PER_SOL;
937 let validator_stake_lamports = 1_000_000 * LAMPORTS_PER_SOL;
938 let mint_lamports = 500_000_000 * LAMPORTS_PER_SOL;
939
940 let mut feature_set = FeatureSet::all_enabled();
942 for feature in &config.deactivate_feature_set {
943 if FEATURE_NAMES.contains_key(feature) {
944 feature_set.deactivate(feature);
945 info!("Feature for {feature:?} deactivated");
946 } else {
947 warn!("Feature {feature:?} set for deactivation is not a known Feature public key",)
948 }
949 }
950
951 let runtime_features = feature_set.runtime_features();
952 let program_runtime_environment = create_program_runtime_environment_v1(
953 &runtime_features,
954 &SVMTransactionExecutionBudget::new_with_defaults(
955 runtime_features.raise_cpi_nesting_limit_to_8,
956 ),
957 true,
958 false,
959 )?;
960 let program_runtime_environment = Arc::new(program_runtime_environment);
961
962 let mut accounts = config.accounts.clone();
963 for (address, account) in solana_program_binaries::spl_programs(&config.rent) {
964 accounts.entry(address).or_insert(account);
965 }
966 for (address, account) in
967 solana_program_binaries::core_bpf_programs(&config.rent, |feature_id| {
968 feature_set.is_active(feature_id)
969 })
970 {
971 accounts.entry(address).or_insert(account);
972 }
973 for upgradeable_program in &config.upgradeable_programs {
974 let data = solana_program_test::read_file(&upgradeable_program.program_path);
975 let executable =
976 Executable::<InvokeContext>::from_elf(&data, program_runtime_environment.clone())
977 .map_err(|err| format!("ELF error: {err}"))?;
978 executable
979 .verify::<RequisiteVerifier>()
980 .map_err(|err| format!("ELF error: {err}"))?;
981
982 let (programdata_address, _) = Pubkey::find_program_address(
983 &[upgradeable_program.program_id.as_ref()],
984 &upgradeable_program.loader,
985 );
986 let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
987 slot: 0,
988 upgrade_authority_address: Some(upgradeable_program.upgrade_authority),
989 })
990 .unwrap();
991 program_data.extend_from_slice(&data);
992 accounts.insert(
993 programdata_address,
994 AccountSharedData::from(Account {
995 lamports: Rent::default().minimum_balance(program_data.len()).max(1),
996 data: program_data,
997 owner: upgradeable_program.loader,
998 executable: false,
999 rent_epoch: 0,
1000 }),
1001 );
1002
1003 let data = bincode::serialize(&UpgradeableLoaderState::Program {
1004 programdata_address,
1005 })
1006 .unwrap();
1007 accounts.insert(
1008 upgradeable_program.program_id,
1009 AccountSharedData::from(Account {
1010 lamports: Rent::default().minimum_balance(data.len()).max(1),
1011 data,
1012 owner: upgradeable_program.loader,
1013 executable: true,
1014 rent_epoch: 0,
1015 }),
1016 );
1017 }
1018
1019 let mut genesis_config = create_genesis_config_with_leader_ex(
1020 mint_lamports,
1021 &mint_address,
1022 &validator_identity.pubkey(),
1023 &validator_vote_account.pubkey(),
1024 &validator_stake_account.pubkey(),
1025 None,
1026 validator_stake_lamports,
1027 validator_identity_lamports,
1028 config.fee_rate_governor.clone(),
1029 config.rent.clone(),
1030 solana_cluster_type::ClusterType::Development,
1031 &feature_set,
1032 accounts.into_iter().collect(),
1033 );
1034 genesis_config.epoch_schedule = config
1035 .epoch_schedule
1036 .as_ref()
1037 .cloned()
1038 .unwrap_or_else(EpochSchedule::without_warmup);
1039
1040 if let Some(ticks_per_slot) = config.ticks_per_slot {
1041 genesis_config.ticks_per_slot = ticks_per_slot;
1042 }
1043
1044 if let Some(inflation) = config.inflation {
1045 genesis_config.inflation = inflation;
1046 }
1047
1048 let ledger_path = match &config.ledger_path {
1049 None => create_new_tmp_ledger!(&genesis_config).0,
1050 Some(ledger_path) => {
1051 if TestValidatorGenesis::ledger_exists(ledger_path) {
1052 return Ok(ledger_path.to_path_buf());
1053 }
1054
1055 let _ = create_new_ledger(
1056 ledger_path,
1057 &genesis_config,
1058 config
1059 .max_genesis_archive_unpacked_size
1060 .unwrap_or(MAX_GENESIS_ARCHIVE_UNPACKED_SIZE),
1061 LedgerColumnOptions::default(),
1062 )
1063 .map_err(|err| {
1064 format!(
1065 "Failed to create ledger at {}: {}",
1066 ledger_path.display(),
1067 err
1068 )
1069 })?;
1070 ledger_path.to_path_buf()
1071 }
1072 };
1073
1074 write_keypair_file(
1075 &validator_identity,
1076 ledger_path.join("validator-keypair.json").to_str().unwrap(),
1077 )?;
1078
1079 write_keypair_file(
1080 &validator_stake_account,
1081 ledger_path
1082 .join("stake-account-keypair.json")
1083 .to_str()
1084 .unwrap(),
1085 )?;
1086
1087 assert!(!TestValidatorGenesis::ledger_exists(&ledger_path));
1089
1090 write_keypair_file(
1091 &validator_vote_account,
1092 ledger_path
1093 .join("vote-account-keypair.json")
1094 .to_str()
1095 .unwrap(),
1096 )?;
1097
1098 Ok(ledger_path)
1099 }
1100
1101 fn start(
1103 mint_address: Pubkey,
1104 config: &TestValidatorGenesis,
1105 socket_addr_space: SocketAddrSpace,
1106 rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
1107 ) -> Result<Self, Box<dyn std::error::Error>> {
1108 let preserve_ledger = config.ledger_path.is_some();
1109 let ledger_path = TestValidator::initialize_ledger(mint_address, config)?;
1110
1111 let validator_identity =
1112 read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
1113 let validator_vote_account = read_keypair_file(
1114 ledger_path
1115 .join("vote-account-keypair.json")
1116 .to_str()
1117 .unwrap(),
1118 )?;
1119 let node = {
1120 let bind_ip_addr = config.node_config.bind_ip_addr;
1121 let validator_node_config = NodeConfig {
1122 bind_ip_addrs: BindIpAddrs::new(vec![bind_ip_addr])?,
1123 gossip_port: config.node_config.gossip_addr.port(),
1124 port_range: config.node_config.port_range,
1125 advertised_ip: bind_ip_addr,
1126 public_tvu_addr: None,
1127 public_tpu_addr: None,
1128 public_tpu_forwards_addr: None,
1129 num_tvu_receive_sockets: NonZero::new(1).unwrap(),
1130 num_tvu_retransmit_sockets: NonZero::new(1).unwrap(),
1131 num_quic_endpoints: NonZero::new(DEFAULT_QUIC_ENDPOINTS)
1132 .expect("Number of QUIC endpoints can not be zero"),
1133 };
1134 let mut node =
1135 Node::new_with_external_ip(&validator_identity.pubkey(), validator_node_config);
1136 let (rpc, rpc_pubsub) = config.rpc_ports.unwrap_or_else(|| {
1137 let rpc_ports: [u16; 2] =
1138 find_available_ports_in_range(bind_ip_addr, config.node_config.port_range)
1139 .unwrap();
1140 (rpc_ports[0], rpc_ports[1])
1141 });
1142 node.info.set_rpc((bind_ip_addr, rpc)).unwrap();
1143 node.info
1144 .set_rpc_pubsub((bind_ip_addr, rpc_pubsub))
1145 .unwrap();
1146 node
1147 };
1148
1149 let vote_account_address = validator_vote_account.pubkey();
1150 let rpc_url = format!("http://{}", node.info.rpc().unwrap());
1151 let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub().unwrap());
1152 let tpu_quic = node.info.tpu(Protocol::QUIC).unwrap();
1153 let gossip = node.info.gossip().unwrap();
1154
1155 {
1156 let mut authorized_voter_keypairs: std::sync::RwLockWriteGuard<'_, Vec<Arc<Keypair>>> =
1157 config.authorized_voter_keypairs.write().unwrap();
1158 if !authorized_voter_keypairs
1159 .iter()
1160 .any(|x| x.pubkey() == vote_account_address)
1161 {
1162 authorized_voter_keypairs.push(Arc::new(validator_vote_account))
1163 }
1164 }
1165
1166 let accounts_db_config = AccountsDbConfig {
1167 index: Some(AccountsIndexConfig::default()),
1168 account_indexes: Some(config.rpc_config.account_indexes.clone()),
1169 ..AccountsDbConfig::default()
1170 };
1171
1172 let runtime_config = RuntimeConfig {
1173 compute_budget: config
1174 .compute_unit_limit
1175 .map(|compute_unit_limit| ComputeBudget {
1176 compute_unit_limit,
1177 ..ComputeBudget::new_with_defaults(
1178 !config
1179 .deactivate_feature_set
1180 .contains(&raise_cpi_nesting_limit_to_8::id()),
1181 !config
1182 .deactivate_feature_set
1183 .contains(&increase_cpi_account_info_limit::id()),
1184 )
1185 }),
1186 log_messages_bytes_limit: config.log_messages_bytes_limit,
1187 transaction_account_lock_limit: config.transaction_account_lock_limit,
1188 };
1189
1190 let mut validator_config = ValidatorConfig {
1191 on_start_geyser_plugin_config_files: config.geyser_plugin_config_files.clone(),
1192 rpc_addrs: Some((
1193 SocketAddr::new(
1194 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1195 node.info.rpc().unwrap().port(),
1196 ),
1197 SocketAddr::new(
1198 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1199 node.info.rpc_pubsub().unwrap().port(),
1200 ),
1201 )),
1202 rpc_config: config.rpc_config.clone(),
1203 pubsub_config: config.pubsub_config.clone(),
1204 account_paths: vec![
1205 create_accounts_run_and_snapshot_dirs(ledger_path.join("accounts"))
1206 .unwrap()
1207 .0,
1208 ],
1209 run_verification: false, snapshot_config: SnapshotConfig {
1211 full_snapshot_archive_interval: SnapshotInterval::Slots(
1212 NonZeroU64::new(100).unwrap(),
1213 ),
1214 incremental_snapshot_archive_interval: SnapshotInterval::Disabled,
1215 bank_snapshots_dir: ledger_path.join(BANK_SNAPSHOTS_DIR),
1216 full_snapshot_archives_dir: ledger_path.to_path_buf(),
1217 incremental_snapshot_archives_dir: ledger_path.to_path_buf(),
1218 ..SnapshotConfig::default()
1219 },
1220 warp_slot: config.warp_slot,
1221 validator_exit: config.validator_exit.clone(),
1222 max_ledger_shreds: config.max_ledger_shreds,
1223 no_wait_for_vote_to_start_leader: true,
1224 staked_nodes_overrides: config.staked_nodes_overrides.clone(),
1225 accounts_db_config,
1226 runtime_config,
1227 enable_scheduler_bindings: config.enable_scheduler_bindings,
1228 ..ValidatorConfig::default_for_test()
1229 };
1230 if let Some(ref tower_storage) = config.tower_storage {
1231 validator_config.tower_storage = tower_storage.clone();
1232 }
1233
1234 let validator = Some(Validator::new(
1235 node,
1236 Arc::new(validator_identity),
1237 &ledger_path,
1238 &vote_account_address,
1239 config.authorized_voter_keypairs.clone(),
1240 vec![],
1241 &validator_config,
1242 true, rpc_to_plugin_manager_receiver,
1244 config.start_progress.clone(),
1245 socket_addr_space,
1246 ValidatorTpuConfig::new_for_tests(),
1247 config.admin_rpc_service_post_init.clone(),
1248 None,
1249 )?);
1250
1251 let test_validator = TestValidator {
1252 ledger_path,
1253 preserve_ledger,
1254 rpc_pubsub_url,
1255 rpc_url,
1256 tpu_quic,
1257 gossip,
1258 validator,
1259 vote_account_address,
1260 };
1261 Ok(test_validator)
1262 }
1263
1264 async fn wait_for_nonzero_fees(&self) {
1268 let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1269 self.rpc_url.clone(),
1270 CommitmentConfig::processed(),
1271 );
1272 let mut message = Message::new(
1273 &[Instruction::new_with_bytes(
1274 Pubkey::new_unique(),
1275 &[],
1276 vec![AccountMeta::new(Pubkey::new_unique(), true)],
1277 )],
1278 None,
1279 );
1280 const MAX_TRIES: u64 = 10;
1281 let mut num_tries = 0;
1282 loop {
1283 num_tries += 1;
1284 if num_tries > MAX_TRIES {
1285 break;
1286 }
1287 println!("Waiting for fees to stabilize {num_tries:?}...");
1288 match rpc_client.get_latest_blockhash().await {
1289 Ok(blockhash) => {
1290 message.recent_blockhash = blockhash;
1291 match rpc_client.get_fee_for_message(&message).await {
1292 Ok(fee) => {
1293 if fee != 0 {
1294 break;
1295 }
1296 }
1297 Err(err) => {
1298 warn!("get_fee_for_message() failed: {err:?}");
1299 break;
1300 }
1301 }
1302 }
1303 Err(err) => {
1304 warn!("get_latest_blockhash() failed: {err:?}");
1305 break;
1306 }
1307 }
1308 sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1309 }
1310 }
1311
1312 async fn wait_for_upgradeable_programs_deployed(
1318 &self,
1319 upgradeable_programs: &[&Pubkey],
1320 payer: &Keypair,
1321 ) -> Result<(), RpcClientError> {
1322 let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1323 self.rpc_url.clone(),
1324 CommitmentConfig::processed(),
1325 );
1326
1327 let mut deployed = vec![false; upgradeable_programs.len()];
1328 const MAX_ATTEMPTS: u64 = 10;
1329
1330 for attempt in 1..=MAX_ATTEMPTS {
1331 let blockhash = rpc_client.get_latest_blockhash().await.unwrap();
1332 for (program_id, is_deployed) in upgradeable_programs.iter().zip(deployed.iter_mut()) {
1333 if *is_deployed {
1334 continue;
1335 }
1336
1337 let transaction = Transaction::new_signed_with_payer(
1338 &[Instruction {
1339 program_id: **program_id,
1340 accounts: vec![],
1341 data: vec![],
1342 }],
1343 Some(&payer.pubkey()),
1344 &[&payer],
1345 blockhash,
1346 );
1347 match rpc_client.simulate_transaction(&transaction).await {
1348 Ok(response) => {
1349 if let Some(e) = response.value.err {
1350 let err_string = format!("{e:?}");
1351 if err_string.contains("Program is not deployed") {
1352 debug!("{program_id:?} - not deployed");
1353 } else if err_string.contains("AccountNotFound") {
1354 return Err(RpcClientError::from(
1356 TransactionError::AccountNotFound,
1357 ));
1358 } else {
1359 *is_deployed = true;
1362 debug!("{program_id:?} - Unexpected error: {e:?}");
1363 }
1364 } else {
1365 *is_deployed = true;
1366 }
1367 }
1368 Err(e) => {
1369 warn!("Failed to simulate transaction: {e:?}");
1370 if attempt == MAX_ATTEMPTS {
1372 return Err(e);
1373 }
1374 }
1375 }
1376 }
1377 if deployed.iter().all(|&deployed| deployed) {
1378 return Ok(());
1379 }
1380
1381 println!("Waiting for programs to be fully deployed {attempt} ...");
1382 sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1383 }
1384 panic!("Timeout waiting for program to become usable");
1385 }
1386
1387 pub fn tpu_quic(&self) -> &SocketAddr {
1389 &self.tpu_quic
1390 }
1391
1392 pub fn gossip(&self) -> &SocketAddr {
1394 &self.gossip
1395 }
1396
1397 pub fn rpc_url(&self) -> String {
1399 self.rpc_url.clone()
1400 }
1401
1402 pub fn rpc_pubsub_url(&self) -> String {
1404 self.rpc_pubsub_url.clone()
1405 }
1406
1407 pub fn vote_account_address(&self) -> Pubkey {
1409 self.vote_account_address
1410 }
1411
1412 pub fn get_rpc_client(&self) -> RpcClient {
1414 RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed())
1415 }
1416
1417 pub fn get_async_rpc_client(&self) -> nonblocking::rpc_client::RpcClient {
1419 nonblocking::rpc_client::RpcClient::new_with_commitment(
1420 self.rpc_url.clone(),
1421 CommitmentConfig::processed(),
1422 )
1423 }
1424
1425 pub fn join(mut self) {
1426 if let Some(validator) = self.validator.take() {
1427 validator.join();
1428 }
1429 }
1430
1431 pub fn cluster_info(&self) -> Arc<ClusterInfo> {
1432 self.validator.as_ref().unwrap().cluster_info.clone()
1433 }
1434
1435 pub fn bank_forks(&self) -> Arc<RwLock<BankForks>> {
1436 self.validator.as_ref().unwrap().bank_forks.clone()
1437 }
1438
1439 pub fn repair_whitelist(&self) -> Arc<RwLock<HashSet<Pubkey>>> {
1440 Arc::new(RwLock::new(HashSet::default()))
1441 }
1442}
1443
1444impl Drop for TestValidator {
1445 fn drop(&mut self) {
1446 if let Some(validator) = self.validator.take() {
1447 validator.close();
1448 }
1449 if !self.preserve_ledger {
1450 remove_dir_all(&self.ledger_path).unwrap_or_else(|err| {
1451 panic!(
1452 "Failed to remove ledger directory {}: {}",
1453 self.ledger_path.display(),
1454 err
1455 )
1456 });
1457 }
1458 }
1459}
1460
1461#[cfg(test)]
1462mod test {
1463 use {super::*, solana_feature_gate_interface::Feature};
1464
1465 #[test]
1466 fn get_health() {
1467 let (test_validator, _payer) = TestValidatorGenesis::default().start();
1468 let rpc_client = test_validator.get_rpc_client();
1469 rpc_client.get_health().expect("health");
1470 }
1471
1472 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1473 async fn nonblocking_get_health() {
1474 let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1475 let rpc_client = test_validator.get_async_rpc_client();
1476 rpc_client.get_health().await.expect("health");
1477 }
1478
1479 #[test]
1480 fn test_upgradeable_program_deploayment() {
1481 let program_id = Pubkey::new_unique();
1482 let (test_validator, payer) = TestValidatorGenesis::default()
1483 .add_program("../programs/bpf-loader-tests/noop", program_id)
1484 .start();
1485 let rpc_client = test_validator.get_rpc_client();
1486
1487 let blockhash = rpc_client.get_latest_blockhash().unwrap();
1488 let transaction = Transaction::new_signed_with_payer(
1489 &[Instruction {
1490 program_id,
1491 accounts: vec![],
1492 data: vec![],
1493 }],
1494 Some(&payer.pubkey()),
1495 &[&payer],
1496 blockhash,
1497 );
1498
1499 assert!(
1500 rpc_client
1501 .send_and_confirm_transaction(&transaction)
1502 .is_ok()
1503 );
1504 }
1505
1506 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1507 async fn test_nonblocking_upgradeable_program_deploayment() {
1508 let program_id = Pubkey::new_unique();
1509 let (test_validator, payer) = TestValidatorGenesis::default()
1510 .add_program("../programs/bpf-loader-tests/noop", program_id)
1511 .start_async()
1512 .await;
1513 let rpc_client = test_validator.get_async_rpc_client();
1514
1515 let blockhash = rpc_client.get_latest_blockhash().await.unwrap();
1516 let transaction = Transaction::new_signed_with_payer(
1517 &[Instruction {
1518 program_id,
1519 accounts: vec![],
1520 data: vec![],
1521 }],
1522 Some(&payer.pubkey()),
1523 &[&payer],
1524 blockhash,
1525 );
1526
1527 assert!(
1528 rpc_client
1529 .send_and_confirm_transaction(&transaction)
1530 .await
1531 .is_ok()
1532 );
1533 }
1534
1535 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1536 #[should_panic]
1537 async fn document_tokio_panic() {
1538 let (_test_validator, _payer) = TestValidatorGenesis::default().start();
1540 }
1541
1542 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1543 async fn test_deactivate_features() {
1544 let mut control = FeatureSet::default().inactive().clone();
1545 let mut deactivate_features = Vec::new();
1546 [
1547 agave_feature_set::deprecate_rewards_sysvar::id(),
1548 agave_feature_set::disable_fees_sysvar::id(),
1549 alpenglow::id(),
1550 agave_feature_set::bls_pubkey_management_in_vote_account::id(),
1551 agave_feature_set::vote_account_initialize_v2::id(),
1552 agave_feature_set::validator_admission_ticket::id(),
1553 ]
1554 .into_iter()
1555 .for_each(|feature| {
1556 control.remove(&feature);
1557 deactivate_features.push(feature);
1558 });
1559
1560 let control: Vec<Pubkey> = control.into_iter().collect();
1562
1563 let (test_validator, _payer) = TestValidatorGenesis::default()
1564 .deactivate_features(&deactivate_features)
1565 .start_async()
1566 .await;
1567
1568 let rpc_client = test_validator.get_async_rpc_client();
1569
1570 let inactive_feature_accounts = rpc_client
1572 .get_multiple_accounts(&deactivate_features)
1573 .await
1574 .unwrap();
1575 for f in inactive_feature_accounts {
1576 assert!(f.is_none());
1577 }
1578
1579 for chunk in control.chunks(100) {
1581 let active_feature_accounts = rpc_client.get_multiple_accounts(chunk).await.unwrap();
1582 for f in active_feature_accounts {
1583 let account = f.unwrap(); let feature_state: Feature = bincode::deserialize(account.data()).unwrap();
1585 assert!(feature_state.activated_at.is_some());
1586 }
1587 }
1588 }
1589
1590 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1591 async fn test_override_feature_account() {
1592 let with_deactivate_flag = agave_feature_set::deprecate_rewards_sysvar::id();
1593 let without_deactivate_flag = agave_feature_set::disable_fees_sysvar::id();
1594
1595 let owner = Pubkey::new_unique();
1596 let account = || AccountSharedData::new(100_000, 0, &owner);
1597
1598 let (test_validator, _payer) = TestValidatorGenesis::default()
1599 .deactivate_features(&[with_deactivate_flag]) .add_accounts([
1601 (with_deactivate_flag, account()), (without_deactivate_flag, account()),
1603 ])
1604 .start_async()
1605 .await;
1606
1607 let rpc_client = test_validator.get_async_rpc_client();
1608
1609 let our_accounts = rpc_client
1610 .get_multiple_accounts(&[with_deactivate_flag, without_deactivate_flag])
1611 .await
1612 .unwrap();
1613
1614 let overridden_account = our_accounts[0].as_ref().unwrap();
1617 assert_eq!(overridden_account.lamports, 100_000);
1618 assert_eq!(overridden_account.data.len(), 0);
1619 assert_eq!(overridden_account.owner, owner);
1620
1621 let feature_account = our_accounts[1].as_ref().unwrap();
1623 assert_eq!(feature_account.owner, solana_sdk_ids::feature::id());
1624 let feature_state: Feature = bincode::deserialize(feature_account.data()).unwrap();
1625 assert!(feature_state.activated_at.is_some());
1626 }
1627
1628 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1629 async fn test_core_bpf_programs() {
1630 let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1631
1632 let rpc_client = test_validator.get_async_rpc_client();
1633
1634 let fetched_programs = rpc_client
1635 .get_multiple_accounts(&[
1636 solana_sdk_ids::address_lookup_table::id(),
1637 solana_sdk_ids::config::id(),
1638 solana_sdk_ids::feature::id(),
1639 solana_sdk_ids::stake::id(),
1640 ])
1641 .await
1642 .unwrap();
1643
1644 let account = fetched_programs[0].as_ref().unwrap();
1646 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1647 assert!(account.executable);
1648
1649 let account = fetched_programs[1].as_ref().unwrap();
1651 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1652 assert!(account.executable);
1653
1654 let account = fetched_programs[2].as_ref().unwrap();
1656 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1657 assert!(account.executable);
1658
1659 let account = fetched_programs[3].as_ref().unwrap();
1661 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1662 assert!(account.executable);
1663 }
1664
1665 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1666 async fn test_wait_for_program_with_unfunded_payer() {
1667 let program_id = Pubkey::new_unique();
1668 let (test_validator, _mint_keypair) = TestValidatorGenesis::default()
1669 .add_program("../programs/bpf-loader-tests/noop", program_id)
1670 .start_async()
1671 .await;
1672
1673 let unfunded_payer = Keypair::new();
1675
1676 let result = test_validator
1678 .wait_for_upgradeable_programs_deployed(&[&program_id], &unfunded_payer)
1679 .await;
1680
1681 let err = result.unwrap_err();
1683 assert!(matches!(
1684 *err.kind,
1685 solana_rpc_client_api::client_error::ErrorKind::TransactionError(
1686 TransactionError::AccountNotFound
1687 )
1688 ));
1689 }
1690}