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