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