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