solana_test_validator/
lib.rs

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