1#![allow(clippy::arithmetic_side_effects)]
2use {
3 agave_feature_set::{FeatureSet, FEATURE_NAMES},
4 base64::{prelude::BASE64_STANDARD, Engine},
5 crossbeam_channel::Receiver,
6 log::*,
7 solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
8 solana_accounts_db::{
9 accounts_db::AccountsDbConfig, accounts_index::AccountsIndexConfig,
10 hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
11 utils::create_accounts_run_and_snapshot_dirs,
12 },
13 solana_cli_output::CliAccount,
14 solana_clock::{Slot, DEFAULT_MS_PER_SLOT},
15 solana_commitment_config::CommitmentConfig,
16 solana_compute_budget::compute_budget::ComputeBudget,
17 solana_core::{
18 admin_rpc_post_init::AdminRpcRequestMetadataPostInit,
19 consensus::tower_storage::TowerStorage,
20 validator::{Validator, ValidatorConfig, ValidatorStartProgress, ValidatorTpuConfig},
21 },
22 solana_epoch_schedule::EpochSchedule,
23 solana_fee_calculator::FeeRateGovernor,
24 solana_geyser_plugin_manager::{
25 geyser_plugin_manager::GeyserPluginManager, GeyserPluginManagerRequest,
26 },
27 solana_gossip::{
28 cluster_info::{ClusterInfo, Node},
29 contact_info::Protocol,
30 socketaddr,
31 },
32 solana_instruction::{AccountMeta, Instruction},
33 solana_keypair::{read_keypair_file, write_keypair_file, Keypair},
34 solana_ledger::{
35 blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions,
36 create_new_tmp_ledger,
37 },
38 solana_loader_v3_interface::state::UpgradeableLoaderState,
39 solana_message::Message,
40 solana_native_token::sol_to_lamports,
41 solana_net_utils::PortRange,
42 solana_pubkey::Pubkey,
43 solana_rent::Rent,
44 solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig},
45 solana_rpc_client::{nonblocking, rpc_client::RpcClient},
46 solana_rpc_client_api::request::MAX_MULTIPLE_ACCOUNTS,
47 solana_runtime::{
48 bank_forks::BankForks,
49 genesis_utils::{self, create_genesis_config_with_leader_ex_no_features},
50 runtime_config::RuntimeConfig,
51 snapshot_config::SnapshotConfig,
52 },
53 solana_sdk_ids::address_lookup_table,
54 solana_signer::Signer,
55 solana_streamer::socket::SocketAddrSpace,
56 solana_tpu_client::tpu_client::DEFAULT_TPU_ENABLE_UDP,
57 solana_validator_exit::Exit,
58 std::{
59 collections::{HashMap, HashSet},
60 ffi::OsStr,
61 fmt::Display,
62 fs::{self, remove_dir_all, File},
63 io::Read,
64 net::{IpAddr, Ipv4Addr, SocketAddr},
65 path::{Path, PathBuf},
66 str::FromStr,
67 sync::{Arc, RwLock},
68 time::Duration,
69 },
70 tokio::time::sleep,
71};
72
73#[derive(Clone)]
74pub struct AccountInfo<'a> {
75 pub address: Option<Pubkey>,
76 pub filename: &'a str,
77}
78
79#[derive(Clone)]
80pub struct UpgradeableProgramInfo {
81 pub program_id: Pubkey,
82 pub loader: Pubkey,
83 pub upgrade_authority: Pubkey,
84 pub program_path: PathBuf,
85}
86
87#[derive(Debug)]
88pub struct TestValidatorNodeConfig {
89 gossip_addr: SocketAddr,
90 port_range: PortRange,
91 bind_ip_addr: IpAddr,
92}
93
94impl Default for TestValidatorNodeConfig {
95 fn default() -> Self {
96 let bind_ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
97 #[cfg(not(debug_assertions))]
98 let port_range = solana_net_utils::VALIDATOR_PORT_RANGE;
99 #[cfg(debug_assertions)]
100 let port_range = solana_net_utils::sockets::localhost_port_range_for_tests();
101 Self {
102 gossip_addr: socketaddr!(Ipv4Addr::LOCALHOST, port_range.0),
103 port_range,
104 bind_ip_addr,
105 }
106 }
107}
108
109pub struct TestValidatorGenesis {
110 fee_rate_governor: FeeRateGovernor,
111 ledger_path: Option<PathBuf>,
112 tower_storage: Option<Arc<dyn TowerStorage>>,
113 pub rent: Rent,
114 rpc_config: JsonRpcConfig,
115 pubsub_config: PubSubConfig,
116 rpc_ports: Option<(u16, u16)>, warp_slot: Option<Slot>,
118 accounts: HashMap<Pubkey, AccountSharedData>,
119 upgradeable_programs: Vec<UpgradeableProgramInfo>,
120 ticks_per_slot: Option<u64>,
121 epoch_schedule: Option<EpochSchedule>,
122 node_config: TestValidatorNodeConfig,
123 pub validator_exit: Arc<RwLock<Exit>>,
124 pub start_progress: Arc<RwLock<ValidatorStartProgress>>,
125 pub authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
126 pub staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
127 pub max_ledger_shreds: Option<u64>,
128 pub max_genesis_archive_unpacked_size: Option<u64>,
129 pub geyser_plugin_config_files: Option<Vec<PathBuf>>,
130 deactivate_feature_set: HashSet<Pubkey>,
131 compute_unit_limit: Option<u64>,
132 pub log_messages_bytes_limit: Option<usize>,
133 pub transaction_account_lock_limit: Option<usize>,
134 pub tpu_enable_udp: bool,
135 pub geyser_plugin_manager: Arc<RwLock<GeyserPluginManager>>,
136 admin_rpc_service_post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
137}
138
139impl Default for TestValidatorGenesis {
140 fn default() -> Self {
141 Self {
142 fee_rate_governor: FeeRateGovernor::default(),
143 ledger_path: Option::<PathBuf>::default(),
144 tower_storage: Option::<Arc<dyn TowerStorage>>::default(),
145 rent: Rent::default(),
146 rpc_config: JsonRpcConfig::default_for_test(),
147 pubsub_config: PubSubConfig::default(),
148 rpc_ports: Option::<(u16, u16)>::default(),
149 warp_slot: Option::<Slot>::default(),
150 accounts: HashMap::<Pubkey, AccountSharedData>::default(),
151 upgradeable_programs: Vec::<UpgradeableProgramInfo>::default(),
152 ticks_per_slot: Option::<u64>::default(),
153 epoch_schedule: Option::<EpochSchedule>::default(),
154 node_config: TestValidatorNodeConfig::default(),
155 validator_exit: Arc::<RwLock<Exit>>::default(),
156 start_progress: Arc::<RwLock<ValidatorStartProgress>>::default(),
157 authorized_voter_keypairs: Arc::<RwLock<Vec<Arc<Keypair>>>>::default(),
158 staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
159 max_ledger_shreds: Option::<u64>::default(),
160 max_genesis_archive_unpacked_size: Option::<u64>::default(),
161 geyser_plugin_config_files: Option::<Vec<PathBuf>>::default(),
162 deactivate_feature_set: HashSet::<Pubkey>::default(),
163 compute_unit_limit: Option::<u64>::default(),
164 log_messages_bytes_limit: Option::<usize>::default(),
165 transaction_account_lock_limit: Option::<usize>::default(),
166 tpu_enable_udp: DEFAULT_TPU_ENABLE_UDP,
167 geyser_plugin_manager: Arc::new(RwLock::new(GeyserPluginManager::new())),
168 admin_rpc_service_post_init:
169 Arc::<RwLock<Option<AdminRpcRequestMetadataPostInit>>>::default(),
170 }
171 }
172}
173
174fn try_transform_program_data(
175 address: &Pubkey,
176 account: &mut AccountSharedData,
177) -> Result<(), String> {
178 if account.owner() == &solana_sdk_ids::bpf_loader_upgradeable::id() {
179 let programdata_offset = UpgradeableLoaderState::size_of_programdata_metadata();
180 let programdata_meta = account.data().get(0..programdata_offset).ok_or(format!(
181 "Failed to get upgradeable programdata data from {address}"
182 ))?;
183 if let Ok(UpgradeableLoaderState::ProgramData {
186 upgrade_authority_address,
187 ..
188 }) = bincode::deserialize::<UpgradeableLoaderState>(programdata_meta)
189 {
190 bincode::serialize_into(
193 account.data_as_mut_slice(),
194 &UpgradeableLoaderState::ProgramData {
195 slot: 0,
196 upgrade_authority_address,
197 },
198 )
199 .map_err(|_| format!("Failed to write to upgradeable programdata account {address}"))
200 } else {
201 Err(format!(
202 "Failed to read upgradeable programdata account {address}"
203 ))
204 }
205 } else {
206 Err(format!("Account {address} not owned by upgradeable loader"))
207 }
208}
209
210impl TestValidatorGenesis {
211 pub fn deactivate_features(&mut self, deactivate_list: &[Pubkey]) -> &mut Self {
215 self.deactivate_feature_set.extend(deactivate_list);
216 self
217 }
218 pub fn ledger_path<P: Into<PathBuf>>(&mut self, ledger_path: P) -> &mut Self {
219 self.ledger_path = Some(ledger_path.into());
220 self
221 }
222
223 pub fn tower_storage(&mut self, tower_storage: Arc<dyn TowerStorage>) -> &mut Self {
224 self.tower_storage = Some(tower_storage);
225 self
226 }
227
228 pub fn ledger_exists(ledger_path: &Path) -> bool {
230 ledger_path.join("vote-account-keypair.json").exists()
231 }
232
233 pub fn tpu_enable_udp(&mut self, tpu_enable_udp: bool) -> &mut Self {
234 self.tpu_enable_udp = tpu_enable_udp;
235 self
236 }
237
238 pub fn fee_rate_governor(&mut self, fee_rate_governor: FeeRateGovernor) -> &mut Self {
239 self.fee_rate_governor = fee_rate_governor;
240 self
241 }
242
243 pub fn ticks_per_slot(&mut self, ticks_per_slot: u64) -> &mut Self {
244 self.ticks_per_slot = Some(ticks_per_slot);
245 self
246 }
247
248 pub fn epoch_schedule(&mut self, epoch_schedule: EpochSchedule) -> &mut Self {
249 self.epoch_schedule = Some(epoch_schedule);
250 self
251 }
252
253 pub fn rent(&mut self, rent: Rent) -> &mut Self {
254 self.rent = rent;
255 self
256 }
257
258 pub fn rpc_config(&mut self, rpc_config: JsonRpcConfig) -> &mut Self {
259 self.rpc_config = rpc_config;
260 self
261 }
262
263 pub fn pubsub_config(&mut self, pubsub_config: PubSubConfig) -> &mut Self {
264 self.pubsub_config = pubsub_config;
265 self
266 }
267
268 pub fn rpc_port(&mut self, rpc_port: u16) -> &mut Self {
269 self.rpc_ports = Some((rpc_port, rpc_port + 1));
270 self
271 }
272
273 pub fn faucet_addr(&mut self, faucet_addr: Option<SocketAddr>) -> &mut Self {
274 self.rpc_config.faucet_addr = faucet_addr;
275 self
276 }
277
278 pub fn warp_slot(&mut self, warp_slot: Slot) -> &mut Self {
279 self.warp_slot = Some(warp_slot);
280 self
281 }
282
283 pub fn gossip_host(&mut self, gossip_host: IpAddr) -> &mut Self {
284 self.node_config.gossip_addr.set_ip(gossip_host);
285 self
286 }
287
288 pub fn gossip_port(&mut self, gossip_port: u16) -> &mut Self {
289 self.node_config.gossip_addr.set_port(gossip_port);
290 self
291 }
292
293 pub fn port_range(&mut self, port_range: PortRange) -> &mut Self {
294 self.node_config.port_range = port_range;
295 self
296 }
297
298 pub fn bind_ip_addr(&mut self, bind_ip_addr: IpAddr) -> &mut Self {
299 self.node_config.bind_ip_addr = bind_ip_addr;
300 self
301 }
302
303 pub fn compute_unit_limit(&mut self, compute_unit_limit: u64) -> &mut Self {
304 self.compute_unit_limit = Some(compute_unit_limit);
305 self
306 }
307
308 pub fn add_account(&mut self, address: Pubkey, account: AccountSharedData) -> &mut Self {
310 self.accounts.insert(address, account);
311 self
312 }
313
314 pub fn add_accounts<T>(&mut self, accounts: T) -> &mut Self
315 where
316 T: IntoIterator<Item = (Pubkey, AccountSharedData)>,
317 {
318 for (address, account) in accounts {
319 self.add_account(address, account);
320 }
321 self
322 }
323
324 fn clone_accounts_and_transform<T, F>(
325 &mut self,
326 addresses: T,
327 rpc_client: &RpcClient,
328 skip_missing: bool,
329 transform: F,
330 ) -> Result<&mut Self, String>
331 where
332 T: IntoIterator<Item = Pubkey>,
333 F: Fn(&Pubkey, Account) -> Result<AccountSharedData, String>,
334 {
335 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
336 for chunk in addresses.chunks(MAX_MULTIPLE_ACCOUNTS) {
337 info!("Fetching {:?} over RPC...", chunk);
338 let responses = rpc_client
339 .get_multiple_accounts(chunk)
340 .map_err(|err| format!("Failed to fetch: {err}"))?;
341 for (address, res) in chunk.iter().zip(responses) {
342 if let Some(account) = res {
343 self.add_account(*address, transform(address, account)?);
344 } else if skip_missing {
345 warn!("Could not find {}, skipping.", address);
346 } else {
347 return Err(format!("Failed to fetch {address}"));
348 }
349 }
350 }
351 Ok(self)
352 }
353
354 pub fn clone_accounts<T>(
355 &mut self,
356 addresses: T,
357 rpc_client: &RpcClient,
358 skip_missing: bool,
359 ) -> Result<&mut Self, String>
360 where
361 T: IntoIterator<Item = Pubkey>,
362 {
363 self.clone_accounts_and_transform(
364 addresses,
365 rpc_client,
366 skip_missing,
367 |address, account| {
368 let mut account_shared_data = AccountSharedData::from(account);
369 try_transform_program_data(address, &mut account_shared_data).ok();
371 Ok(account_shared_data)
372 },
373 )
374 }
375
376 pub fn deep_clone_address_lookup_table_accounts<T>(
377 &mut self,
378 addresses: T,
379 rpc_client: &RpcClient,
380 ) -> Result<&mut Self, String>
381 where
382 T: IntoIterator<Item = Pubkey>,
383 {
384 const LOOKUP_TABLE_META_SIZE: usize = 56;
385 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
386 let mut alt_entries: Vec<Pubkey> = Vec::new();
387
388 for chunk in addresses.chunks(MAX_MULTIPLE_ACCOUNTS) {
389 info!("Fetching {:?} over RPC...", chunk);
390 let responses = rpc_client
391 .get_multiple_accounts(chunk)
392 .map_err(|err| format!("Failed to fetch: {err}"))?;
393 for (address, res) in chunk.iter().zip(responses) {
394 if let Some(account) = res {
395 if address_lookup_table::check_id(account.owner()) {
396 let raw_addresses_data = account
397 .data()
398 .get(LOOKUP_TABLE_META_SIZE..)
399 .ok_or(format!("Failed to get addresses data from {address}"))?;
400
401 if raw_addresses_data.len() % std::mem::size_of::<Pubkey>() != 0 {
402 return Err(format!("Invalid alt account data length for {address}"));
403 }
404
405 for address_slice in
406 raw_addresses_data.chunks_exact(std::mem::size_of::<Pubkey>())
407 {
408 let address = Pubkey::try_from(address_slice).unwrap();
410 alt_entries.push(address);
411 }
412 self.add_account(*address, AccountSharedData::from(account));
413 } else {
414 return Err(format!("Account {address} is not an address lookup table"));
415 }
416 } else {
417 return Err(format!("Failed to fetch {address}"));
418 }
419 }
420 }
421
422 self.clone_accounts(alt_entries, rpc_client, true)
423 }
424
425 pub fn clone_programdata_accounts<T>(
426 &mut self,
427 addresses: T,
428 rpc_client: &RpcClient,
429 skip_missing: bool,
430 ) -> Result<&mut Self, String>
431 where
432 T: IntoIterator<Item = Pubkey>,
433 {
434 self.clone_accounts_and_transform(
435 addresses,
436 rpc_client,
437 skip_missing,
438 |address, account| {
439 let mut account_shared_data = AccountSharedData::from(account);
440 try_transform_program_data(address, &mut account_shared_data)?;
441 Ok(account_shared_data)
442 },
443 )
444 }
445
446 pub fn clone_upgradeable_programs<T>(
447 &mut self,
448 addresses: T,
449 rpc_client: &RpcClient,
450 ) -> Result<&mut Self, String>
451 where
452 T: IntoIterator<Item = Pubkey>,
453 {
454 let addresses: Vec<Pubkey> = addresses.into_iter().collect();
455 self.clone_accounts(addresses.clone(), rpc_client, false)?;
456
457 let mut programdata_addresses: HashSet<Pubkey> = HashSet::new();
458 for address in addresses {
459 let account = self.accounts.get(&address).unwrap();
460
461 if let Ok(UpgradeableLoaderState::Program {
462 programdata_address,
463 }) = account.deserialize_data()
464 {
465 programdata_addresses.insert(programdata_address);
466 } else {
467 return Err(format!(
468 "Failed to read upgradeable program account {address}",
469 ));
470 }
471 }
472
473 self.clone_programdata_accounts(programdata_addresses, rpc_client, false)?;
474
475 Ok(self)
476 }
477
478 pub fn clone_feature_set(&mut self, rpc_client: &RpcClient) -> Result<&mut Self, String> {
479 for feature_ids in FEATURE_NAMES
480 .keys()
481 .cloned()
482 .collect::<Vec<Pubkey>>()
483 .chunks(MAX_MULTIPLE_ACCOUNTS)
484 {
485 rpc_client
486 .get_multiple_accounts(feature_ids)
487 .map_err(|err| format!("Failed to fetch: {err}"))?
488 .into_iter()
489 .zip(feature_ids)
490 .for_each(|(maybe_account, feature_id)| {
491 if maybe_account
492 .as_ref()
493 .and_then(solana_feature_gate_interface::from_account)
494 .and_then(|feature| feature.activated_at)
495 .is_none()
496 {
497 self.deactivate_feature_set.insert(*feature_id);
498 }
499 });
500 }
501 Ok(self)
502 }
503
504 pub fn add_accounts_from_json_files(
505 &mut self,
506 accounts: &[AccountInfo],
507 ) -> Result<&mut Self, String> {
508 for account in accounts {
509 let Some(account_path) = solana_program_test::find_file(account.filename) else {
510 return Err(format!("Unable to locate {}", account.filename));
511 };
512 let mut file = File::open(&account_path).unwrap();
513 let mut account_info_raw = String::new();
514 file.read_to_string(&mut account_info_raw).unwrap();
515
516 let result: serde_json::Result<CliAccount> = serde_json::from_str(&account_info_raw);
517 let account_info = match result {
518 Err(err) => {
519 return Err(format!(
520 "Unable to deserialize {}: {}",
521 account_path.to_str().unwrap(),
522 err
523 ));
524 }
525 Ok(deserialized) => deserialized,
526 };
527
528 let address = account.address.unwrap_or_else(|| {
529 Pubkey::from_str(account_info.keyed_account.pubkey.as_str()).unwrap()
530 });
531 let account = account_info
532 .keyed_account
533 .account
534 .decode::<AccountSharedData>()
535 .unwrap();
536
537 self.add_account(address, account);
538 }
539 Ok(self)
540 }
541
542 pub fn add_accounts_from_directories<T, P>(&mut self, dirs: T) -> Result<&mut Self, String>
543 where
544 T: IntoIterator<Item = P>,
545 P: AsRef<Path> + Display,
546 {
547 let mut json_files: HashSet<String> = HashSet::new();
548 for dir in dirs {
549 let matched_files = match fs::read_dir(&dir) {
550 Ok(dir) => dir,
551 Err(e) => return Err(format!("Cannot read directory {}: {}", &dir, e)),
552 }
553 .flatten()
554 .map(|entry| entry.path())
555 .filter(|path| path.is_file() && path.extension() == Some(OsStr::new("json")))
556 .map(|path| String::from(path.to_string_lossy()));
557
558 json_files.extend(matched_files);
559 }
560
561 debug!("account files found: {:?}", json_files);
562
563 let accounts: Vec<_> = json_files
564 .iter()
565 .map(|filename| AccountInfo {
566 address: None,
567 filename,
568 })
569 .collect();
570
571 self.add_accounts_from_json_files(&accounts)?;
572
573 Ok(self)
574 }
575
576 pub fn add_account_with_file_data(
578 &mut self,
579 address: Pubkey,
580 lamports: u64,
581 owner: Pubkey,
582 filename: &str,
583 ) -> &mut Self {
584 self.add_account(
585 address,
586 AccountSharedData::from(Account {
587 lamports,
588 data: solana_program_test::read_file(
589 solana_program_test::find_file(filename).unwrap_or_else(|| {
590 panic!("Unable to locate {filename}");
591 }),
592 ),
593 owner,
594 executable: false,
595 rent_epoch: 0,
596 }),
597 )
598 }
599
600 pub fn add_account_with_base64_data(
603 &mut self,
604 address: Pubkey,
605 lamports: u64,
606 owner: Pubkey,
607 data_base64: &str,
608 ) -> &mut Self {
609 self.add_account(
610 address,
611 AccountSharedData::from(Account {
612 lamports,
613 data: BASE64_STANDARD
614 .decode(data_base64)
615 .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
616 owner,
617 executable: false,
618 rent_epoch: 0,
619 }),
620 )
621 }
622
623 pub fn add_program(&mut self, program_name: &str, program_id: Pubkey) -> &mut Self {
628 let program_path = solana_program_test::find_file(&format!("{program_name}.so"))
629 .unwrap_or_else(|| panic!("Unable to locate program {program_name}"));
630
631 self.upgradeable_programs.push(UpgradeableProgramInfo {
632 program_id,
633 loader: solana_sdk_ids::bpf_loader_upgradeable::id(),
634 upgrade_authority: Pubkey::default(),
635 program_path,
636 });
637 self
638 }
639
640 pub fn add_upgradeable_programs_with_path(
642 &mut self,
643 programs: &[UpgradeableProgramInfo],
644 ) -> &mut Self {
645 for program in programs {
646 self.upgradeable_programs.push(program.clone());
647 }
648 self
649 }
650
651 pub fn start_with_mint_address(
655 &self,
656 mint_address: Pubkey,
657 socket_addr_space: SocketAddrSpace,
658 ) -> Result<TestValidator, Box<dyn std::error::Error>> {
659 self.start_with_mint_address_and_geyser_plugin_rpc(mint_address, socket_addr_space, None)
660 }
661
662 pub fn start_with_mint_address_and_geyser_plugin_rpc(
667 &self,
668 mint_address: Pubkey,
669 socket_addr_space: SocketAddrSpace,
670 rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
671 ) -> Result<TestValidator, Box<dyn std::error::Error>> {
672 TestValidator::start(
673 mint_address,
674 self,
675 socket_addr_space,
676 rpc_to_plugin_manager_receiver,
677 )
678 .inspect(|test_validator| {
679 let runtime = tokio::runtime::Builder::new_current_thread()
680 .enable_io()
681 .enable_time()
682 .build()
683 .unwrap();
684 runtime.block_on(test_validator.wait_for_nonzero_fees());
685 })
686 }
687
688 pub fn start(&self) -> (TestValidator, Keypair) {
695 self.start_with_socket_addr_space(SocketAddrSpace::new(true))
696 }
697
698 pub fn start_with_socket_addr_space(
705 &self,
706 socket_addr_space: SocketAddrSpace,
707 ) -> (TestValidator, Keypair) {
708 let mint_keypair = Keypair::new();
709 self.start_with_mint_address(mint_keypair.pubkey(), socket_addr_space)
710 .map(|test_validator| (test_validator, mint_keypair))
711 .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"))
712 }
713
714 pub async fn start_async(&self) -> (TestValidator, Keypair) {
715 self.start_async_with_socket_addr_space(SocketAddrSpace::new(
716 true,
717 ))
718 .await
719 }
720
721 pub async fn start_async_with_socket_addr_space(
722 &self,
723 socket_addr_space: SocketAddrSpace,
724 ) -> (TestValidator, Keypair) {
725 let mint_keypair = Keypair::new();
726 match TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space, None) {
727 Ok(test_validator) => {
728 test_validator.wait_for_nonzero_fees().await;
729 (test_validator, mint_keypair)
730 }
731 Err(err) => panic!("Test validator failed to start: {err}"),
732 }
733 }
734}
735
736pub struct TestValidator {
737 ledger_path: PathBuf,
738 preserve_ledger: bool,
739 rpc_pubsub_url: String,
740 rpc_url: String,
741 tpu: SocketAddr,
742 gossip: SocketAddr,
743 validator: Option<Validator>,
744 vote_account_address: Pubkey,
745}
746
747impl TestValidator {
748 pub fn with_no_fees(
753 mint_address: Pubkey,
754 faucet_addr: Option<SocketAddr>,
755 socket_addr_space: SocketAddrSpace,
756 ) -> Self {
757 TestValidatorGenesis::default()
758 .fee_rate_governor(FeeRateGovernor::new(0, 0))
759 .rent(Rent {
760 lamports_per_byte_year: 1,
761 exemption_threshold: 1.0,
762 ..Rent::default()
763 })
764 .faucet_addr(faucet_addr)
765 .start_with_mint_address(mint_address, socket_addr_space)
766 .expect("validator start failed")
767 }
768
769 pub fn with_no_fees_udp(
771 mint_address: Pubkey,
772 faucet_addr: Option<SocketAddr>,
773 socket_addr_space: SocketAddrSpace,
774 ) -> Self {
775 TestValidatorGenesis::default()
776 .tpu_enable_udp(true)
777 .fee_rate_governor(FeeRateGovernor::new(0, 0))
778 .rent(Rent {
779 lamports_per_byte_year: 1,
780 exemption_threshold: 1.0,
781 ..Rent::default()
782 })
783 .faucet_addr(faucet_addr)
784 .start_with_mint_address(mint_address, socket_addr_space)
785 .expect("validator start failed")
786 }
787
788 pub fn with_custom_fees(
793 mint_address: Pubkey,
794 target_lamports_per_signature: u64,
795 faucet_addr: Option<SocketAddr>,
796 socket_addr_space: SocketAddrSpace,
797 ) -> Self {
798 TestValidatorGenesis::default()
799 .fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
800 .rent(Rent {
801 lamports_per_byte_year: 1,
802 exemption_threshold: 1.0,
803 ..Rent::default()
804 })
805 .faucet_addr(faucet_addr)
806 .start_with_mint_address(mint_address, socket_addr_space)
807 .expect("validator start failed")
808 }
809
810 pub fn set_startup_verification_complete_for_tests(&self) {
812 self.bank_forks()
813 .read()
814 .unwrap()
815 .root_bank()
816 .set_startup_verification_complete();
817 }
818
819 fn initialize_ledger(
826 mint_address: Pubkey,
827 config: &TestValidatorGenesis,
828 ) -> Result<PathBuf, Box<dyn std::error::Error>> {
829 let validator_identity = Keypair::new();
830 let validator_vote_account = Keypair::new();
831 let validator_stake_account = Keypair::new();
832 let validator_identity_lamports = sol_to_lamports(500.);
833 let validator_stake_lamports = sol_to_lamports(1_000_000.);
834 let mint_lamports = sol_to_lamports(500_000_000.);
835
836 let mut feature_set = FeatureSet::default().inactive().clone();
838 for feature in &config.deactivate_feature_set {
839 if feature_set.remove(feature) {
840 info!("Feature for {:?} deactivated", feature)
841 } else {
842 warn!(
843 "Feature {:?} set for deactivation is not a known Feature public key",
844 feature,
845 )
846 }
847 }
848
849 let mut accounts = config.accounts.clone();
850 for (address, account) in solana_program_test::programs::spl_programs(&config.rent) {
851 accounts.entry(address).or_insert(account);
852 }
853 for (address, account) in
854 solana_program_test::programs::core_bpf_programs(&config.rent, |feature_id| {
855 feature_set.contains(feature_id)
856 })
857 {
858 accounts.entry(address).or_insert(account);
859 }
860 for upgradeable_program in &config.upgradeable_programs {
861 let data = solana_program_test::read_file(&upgradeable_program.program_path);
862 let (programdata_address, _) = Pubkey::find_program_address(
863 &[upgradeable_program.program_id.as_ref()],
864 &upgradeable_program.loader,
865 );
866 let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
867 slot: 0,
868 upgrade_authority_address: Some(upgradeable_program.upgrade_authority),
869 })
870 .unwrap();
871 program_data.extend_from_slice(&data);
872 accounts.insert(
873 programdata_address,
874 AccountSharedData::from(Account {
875 lamports: Rent::default().minimum_balance(program_data.len()).max(1),
876 data: program_data,
877 owner: upgradeable_program.loader,
878 executable: false,
879 rent_epoch: 0,
880 }),
881 );
882
883 let data = bincode::serialize(&UpgradeableLoaderState::Program {
884 programdata_address,
885 })
886 .unwrap();
887 accounts.insert(
888 upgradeable_program.program_id,
889 AccountSharedData::from(Account {
890 lamports: Rent::default().minimum_balance(data.len()).max(1),
891 data,
892 owner: upgradeable_program.loader,
893 executable: true,
894 rent_epoch: 0,
895 }),
896 );
897 }
898
899 let mut genesis_config = create_genesis_config_with_leader_ex_no_features(
900 mint_lamports,
901 &mint_address,
902 &validator_identity.pubkey(),
903 &validator_vote_account.pubkey(),
904 &validator_stake_account.pubkey(),
905 validator_stake_lamports,
906 validator_identity_lamports,
907 config.fee_rate_governor.clone(),
908 config.rent.clone(),
909 solana_cluster_type::ClusterType::Development,
910 accounts.into_iter().collect(),
911 );
912 genesis_config.epoch_schedule = config
913 .epoch_schedule
914 .as_ref()
915 .cloned()
916 .unwrap_or_else(EpochSchedule::without_warmup);
917
918 if let Some(ticks_per_slot) = config.ticks_per_slot {
919 genesis_config.ticks_per_slot = ticks_per_slot;
920 }
921
922 for feature in feature_set {
923 genesis_utils::activate_feature(&mut genesis_config, feature);
924 }
925
926 let ledger_path = match &config.ledger_path {
927 None => create_new_tmp_ledger!(&genesis_config).0,
928 Some(ledger_path) => {
929 if TestValidatorGenesis::ledger_exists(ledger_path) {
930 return Ok(ledger_path.to_path_buf());
931 }
932
933 let _ = create_new_ledger(
934 ledger_path,
935 &genesis_config,
936 config
937 .max_genesis_archive_unpacked_size
938 .unwrap_or(MAX_GENESIS_ARCHIVE_UNPACKED_SIZE),
939 LedgerColumnOptions::default(),
940 )
941 .map_err(|err| {
942 format!(
943 "Failed to create ledger at {}: {}",
944 ledger_path.display(),
945 err
946 )
947 })?;
948 ledger_path.to_path_buf()
949 }
950 };
951
952 write_keypair_file(
953 &validator_identity,
954 ledger_path.join("validator-keypair.json").to_str().unwrap(),
955 )?;
956
957 write_keypair_file(
958 &validator_stake_account,
959 ledger_path
960 .join("stake-account-keypair.json")
961 .to_str()
962 .unwrap(),
963 )?;
964
965 assert!(!TestValidatorGenesis::ledger_exists(&ledger_path));
967
968 write_keypair_file(
969 &validator_vote_account,
970 ledger_path
971 .join("vote-account-keypair.json")
972 .to_str()
973 .unwrap(),
974 )?;
975
976 Ok(ledger_path)
977 }
978
979 fn start(
981 mint_address: Pubkey,
982 config: &TestValidatorGenesis,
983 socket_addr_space: SocketAddrSpace,
984 rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
985 ) -> Result<Self, Box<dyn std::error::Error>> {
986 let preserve_ledger = config.ledger_path.is_some();
987 let ledger_path = TestValidator::initialize_ledger(mint_address, config)?;
988
989 let validator_identity =
990 read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
991 let validator_vote_account = read_keypair_file(
992 ledger_path
993 .join("vote-account-keypair.json")
994 .to_str()
995 .unwrap(),
996 )?;
997
998 let mut node = Node::new_single_bind(
999 &validator_identity.pubkey(),
1000 &config.node_config.gossip_addr,
1001 config.node_config.port_range,
1002 config.node_config.bind_ip_addr,
1003 );
1004 if let Some((rpc, rpc_pubsub)) = config.rpc_ports {
1005 let addr = node.info.gossip().unwrap().ip();
1006 node.info.set_rpc((addr, rpc)).unwrap();
1007 node.info.set_rpc_pubsub((addr, rpc_pubsub)).unwrap();
1008 }
1009
1010 let vote_account_address = validator_vote_account.pubkey();
1011 let rpc_url = format!("http://{}", node.info.rpc().unwrap());
1012 let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub().unwrap());
1013 let tpu = node.info.tpu(Protocol::UDP).unwrap();
1014 let gossip = node.info.gossip().unwrap();
1015
1016 {
1017 let mut authorized_voter_keypairs = config.authorized_voter_keypairs.write().unwrap();
1018 if !authorized_voter_keypairs
1019 .iter()
1020 .any(|x| x.pubkey() == vote_account_address)
1021 {
1022 authorized_voter_keypairs.push(Arc::new(validator_vote_account))
1023 }
1024 }
1025
1026 let accounts_db_config = Some(AccountsDbConfig {
1027 index: Some(AccountsIndexConfig::default()),
1028 account_indexes: Some(config.rpc_config.account_indexes.clone()),
1029 ..AccountsDbConfig::default()
1030 });
1031
1032 let runtime_config = RuntimeConfig {
1033 compute_budget: config
1034 .compute_unit_limit
1035 .map(|compute_unit_limit| ComputeBudget {
1036 compute_unit_limit,
1037 ..ComputeBudget::default()
1038 }),
1039 log_messages_bytes_limit: config.log_messages_bytes_limit,
1040 transaction_account_lock_limit: config.transaction_account_lock_limit,
1041 };
1042
1043 let mut validator_config = ValidatorConfig {
1044 on_start_geyser_plugin_config_files: config.geyser_plugin_config_files.clone(),
1045 rpc_addrs: Some((
1046 SocketAddr::new(
1047 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1048 node.info.rpc().unwrap().port(),
1049 ),
1050 SocketAddr::new(
1051 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1052 node.info.rpc_pubsub().unwrap().port(),
1053 ),
1054 )),
1055 rpc_config: config.rpc_config.clone(),
1056 pubsub_config: config.pubsub_config.clone(),
1057 account_paths: vec![
1058 create_accounts_run_and_snapshot_dirs(ledger_path.join("accounts"))
1059 .unwrap()
1060 .0,
1061 ],
1062 run_verification: false, snapshot_config: SnapshotConfig {
1064 full_snapshot_archive_interval_slots: 100,
1065 incremental_snapshot_archive_interval_slots: Slot::MAX,
1066 bank_snapshots_dir: ledger_path.join("snapshot"),
1067 full_snapshot_archives_dir: ledger_path.to_path_buf(),
1068 incremental_snapshot_archives_dir: ledger_path.to_path_buf(),
1069 ..SnapshotConfig::default()
1070 },
1071 warp_slot: config.warp_slot,
1072 validator_exit: config.validator_exit.clone(),
1073 max_ledger_shreds: config.max_ledger_shreds,
1074 no_wait_for_vote_to_start_leader: true,
1075 staked_nodes_overrides: config.staked_nodes_overrides.clone(),
1076 accounts_db_config,
1077 runtime_config,
1078 ..ValidatorConfig::default_for_test()
1079 };
1080 if let Some(ref tower_storage) = config.tower_storage {
1081 validator_config.tower_storage = tower_storage.clone();
1082 }
1083
1084 let validator = Some(Validator::new(
1085 node,
1086 Arc::new(validator_identity),
1087 &ledger_path,
1088 &vote_account_address,
1089 config.authorized_voter_keypairs.clone(),
1090 vec![],
1091 &validator_config,
1092 true, rpc_to_plugin_manager_receiver,
1094 config.start_progress.clone(),
1095 socket_addr_space,
1096 ValidatorTpuConfig::new_for_tests(config.tpu_enable_udp),
1097 config.admin_rpc_service_post_init.clone(),
1098 )?);
1099
1100 let test_validator = TestValidator {
1101 ledger_path,
1102 preserve_ledger,
1103 rpc_pubsub_url,
1104 rpc_url,
1105 tpu,
1106 gossip,
1107 validator,
1108 vote_account_address,
1109 };
1110 Ok(test_validator)
1111 }
1112
1113 async fn wait_for_nonzero_fees(&self) {
1117 let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1118 self.rpc_url.clone(),
1119 CommitmentConfig::processed(),
1120 );
1121 let mut message = Message::new(
1122 &[Instruction::new_with_bytes(
1123 Pubkey::new_unique(),
1124 &[],
1125 vec![AccountMeta::new(Pubkey::new_unique(), true)],
1126 )],
1127 None,
1128 );
1129 const MAX_TRIES: u64 = 10;
1130 let mut num_tries = 0;
1131 loop {
1132 num_tries += 1;
1133 if num_tries > MAX_TRIES {
1134 break;
1135 }
1136 println!("Waiting for fees to stabilize {num_tries:?}...");
1137 match rpc_client.get_latest_blockhash().await {
1138 Ok(blockhash) => {
1139 message.recent_blockhash = blockhash;
1140 match rpc_client.get_fee_for_message(&message).await {
1141 Ok(fee) => {
1142 if fee != 0 {
1143 break;
1144 }
1145 }
1146 Err(err) => {
1147 warn!("get_fee_for_message() failed: {:?}", err);
1148 break;
1149 }
1150 }
1151 }
1152 Err(err) => {
1153 warn!("get_latest_blockhash() failed: {:?}", err);
1154 break;
1155 }
1156 }
1157 sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1158 }
1159 }
1160
1161 pub fn tpu(&self) -> &SocketAddr {
1163 &self.tpu
1164 }
1165
1166 pub fn gossip(&self) -> &SocketAddr {
1168 &self.gossip
1169 }
1170
1171 pub fn rpc_url(&self) -> String {
1173 self.rpc_url.clone()
1174 }
1175
1176 pub fn rpc_pubsub_url(&self) -> String {
1178 self.rpc_pubsub_url.clone()
1179 }
1180
1181 pub fn vote_account_address(&self) -> Pubkey {
1183 self.vote_account_address
1184 }
1185
1186 pub fn get_rpc_client(&self) -> RpcClient {
1188 RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed())
1189 }
1190
1191 pub fn get_async_rpc_client(&self) -> nonblocking::rpc_client::RpcClient {
1193 nonblocking::rpc_client::RpcClient::new_with_commitment(
1194 self.rpc_url.clone(),
1195 CommitmentConfig::processed(),
1196 )
1197 }
1198
1199 pub fn join(mut self) {
1200 if let Some(validator) = self.validator.take() {
1201 validator.join();
1202 }
1203 }
1204
1205 pub fn cluster_info(&self) -> Arc<ClusterInfo> {
1206 self.validator.as_ref().unwrap().cluster_info.clone()
1207 }
1208
1209 pub fn bank_forks(&self) -> Arc<RwLock<BankForks>> {
1210 self.validator.as_ref().unwrap().bank_forks.clone()
1211 }
1212
1213 pub fn repair_whitelist(&self) -> Arc<RwLock<HashSet<Pubkey>>> {
1214 Arc::new(RwLock::new(HashSet::default()))
1215 }
1216}
1217
1218impl Drop for TestValidator {
1219 fn drop(&mut self) {
1220 if let Some(validator) = self.validator.take() {
1221 validator.close();
1222 }
1223 if !self.preserve_ledger {
1224 remove_dir_all(&self.ledger_path).unwrap_or_else(|err| {
1225 panic!(
1226 "Failed to remove ledger directory {}: {}",
1227 self.ledger_path.display(),
1228 err
1229 )
1230 });
1231 }
1232 }
1233}
1234
1235#[cfg(test)]
1236mod test {
1237 use {super::*, solana_feature_gate_interface::Feature};
1238
1239 #[test]
1240 fn get_health() {
1241 let (test_validator, _payer) = TestValidatorGenesis::default().start();
1242 test_validator.set_startup_verification_complete_for_tests();
1243 let rpc_client = test_validator.get_rpc_client();
1244 rpc_client.get_health().expect("health");
1245 }
1246
1247 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1248 async fn nonblocking_get_health() {
1249 let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1250 test_validator.set_startup_verification_complete_for_tests();
1251 let rpc_client = test_validator.get_async_rpc_client();
1252 rpc_client.get_health().await.expect("health");
1253 }
1254
1255 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1256 #[should_panic]
1257 async fn document_tokio_panic() {
1258 let (_test_validator, _payer) = TestValidatorGenesis::default().start();
1260 }
1261
1262 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1263 async fn test_deactivate_features() {
1264 let mut control = FeatureSet::default().inactive().clone();
1265 let mut deactivate_features = Vec::new();
1266 [
1267 agave_feature_set::deprecate_rewards_sysvar::id(),
1268 agave_feature_set::disable_fees_sysvar::id(),
1269 ]
1270 .into_iter()
1271 .for_each(|feature| {
1272 control.remove(&feature);
1273 deactivate_features.push(feature);
1274 });
1275
1276 let control: Vec<Pubkey> = control.into_iter().collect();
1278
1279 let (test_validator, _payer) = TestValidatorGenesis::default()
1280 .deactivate_features(&deactivate_features)
1281 .start_async()
1282 .await;
1283
1284 let rpc_client = test_validator.get_async_rpc_client();
1285
1286 let inactive_feature_accounts = rpc_client
1288 .get_multiple_accounts(&deactivate_features)
1289 .await
1290 .unwrap();
1291 for f in inactive_feature_accounts {
1292 assert!(f.is_none());
1293 }
1294
1295 for chunk in control.chunks(100) {
1297 let active_feature_accounts = rpc_client.get_multiple_accounts(chunk).await.unwrap();
1298 for f in active_feature_accounts {
1299 let account = f.unwrap(); let feature_state: Feature = bincode::deserialize(account.data()).unwrap();
1301 assert!(feature_state.activated_at.is_some());
1302 }
1303 }
1304 }
1305
1306 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1307 async fn test_override_feature_account() {
1308 let with_deactivate_flag = agave_feature_set::deprecate_rewards_sysvar::id();
1309 let without_deactivate_flag = agave_feature_set::disable_fees_sysvar::id();
1310
1311 let owner = Pubkey::new_unique();
1312 let account = || AccountSharedData::new(100_000, 0, &owner);
1313
1314 let (test_validator, _payer) = TestValidatorGenesis::default()
1315 .deactivate_features(&[with_deactivate_flag]) .add_accounts([
1317 (with_deactivate_flag, account()), (without_deactivate_flag, account()),
1319 ])
1320 .start_async()
1321 .await;
1322
1323 let rpc_client = test_validator.get_async_rpc_client();
1324
1325 let our_accounts = rpc_client
1326 .get_multiple_accounts(&[with_deactivate_flag, without_deactivate_flag])
1327 .await
1328 .unwrap();
1329
1330 let overriden_account = our_accounts[0].as_ref().unwrap();
1333 assert_eq!(overriden_account.lamports, 100_000);
1334 assert_eq!(overriden_account.data.len(), 0);
1335 assert_eq!(overriden_account.owner, owner);
1336
1337 let feature_account = our_accounts[1].as_ref().unwrap();
1339 assert_eq!(feature_account.owner, solana_sdk_ids::feature::id());
1340 let feature_state: Feature = bincode::deserialize(feature_account.data()).unwrap();
1341 assert!(feature_state.activated_at.is_some());
1342 }
1343
1344 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1345 async fn test_core_bpf_programs() {
1346 let (test_validator, _payer) = TestValidatorGenesis::default()
1347 .deactivate_features(&[
1348 agave_feature_set::migrate_stake_program_to_core_bpf::id(),
1350 ])
1351 .start_async()
1352 .await;
1353
1354 let rpc_client = test_validator.get_async_rpc_client();
1355
1356 let fetched_programs = rpc_client
1357 .get_multiple_accounts(&[
1358 solana_sdk_ids::address_lookup_table::id(),
1359 solana_sdk_ids::config::id(),
1360 solana_sdk_ids::feature::id(),
1361 solana_sdk_ids::stake::id(),
1362 ])
1363 .await
1364 .unwrap();
1365
1366 let account = fetched_programs[0].as_ref().unwrap();
1368 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1369 assert!(account.executable);
1370
1371 let account = fetched_programs[1].as_ref().unwrap();
1373 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1374 assert!(account.executable);
1375
1376 let account = fetched_programs[2].as_ref().unwrap();
1378 assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1379 assert!(account.executable);
1380
1381 let account = fetched_programs[3].as_ref().unwrap();
1383 assert_eq!(account.owner, solana_sdk_ids::native_loader::id());
1384 assert!(account.executable);
1385 }
1386}