Skip to main content

solana_test_validator/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2use {
3    agave_feature_set::{FEATURE_NAMES, FeatureSet, alpenglow, raise_cpi_nesting_limit_to_8},
4    agave_snapshots::{
5        SnapshotInterval, paths::BANK_SNAPSHOTS_DIR, snapshot_config::SnapshotConfig,
6    },
7    arc_swap::ArcSwap,
8    base64::{Engine, prelude::BASE64_STANDARD},
9    crossbeam_channel::Receiver,
10    log::*,
11    solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
12    solana_accounts_db::{
13        accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
14        accounts_index::{AccountsIndexConfig, ScanFilter},
15        utils::create_accounts_run_and_snapshot_dirs,
16    },
17    solana_cli_output::CliAccount,
18    solana_clock::{DEFAULT_MS_PER_SLOT, Slot},
19    solana_commitment_config::CommitmentConfig,
20    solana_compute_budget::compute_budget::ComputeBudget,
21    solana_core::{
22        admin_rpc_post_init::AdminRpcRequestMetadataPostInit,
23        consensus::tower_storage::TowerStorage,
24        validator::{Validator, ValidatorConfig, ValidatorStartProgress, ValidatorTpuConfig},
25    },
26    solana_epoch_schedule::EpochSchedule,
27    solana_fee_calculator::FeeRateGovernor,
28    solana_genesis_utils::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
29    solana_geyser_plugin_manager::{
30        GeyserPluginManagerRequest, geyser_plugin_manager::GeyserPluginManager,
31    },
32    solana_gossip::{
33        cluster_info::{ClusterInfo, NodeConfig},
34        contact_info::Protocol,
35        node::Node,
36    },
37    solana_inflation::Inflation,
38    solana_instruction::Instruction,
39    solana_keypair::{Keypair, read_keypair_file, write_keypair_file},
40    solana_ledger::{
41        blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions,
42        create_new_tmp_ledger,
43    },
44    solana_loader_v3_interface::state::UpgradeableLoaderState,
45    solana_native_token::LAMPORTS_PER_SOL,
46    solana_net_utils::{
47        PortRange, SocketAddrSpace, find_available_ports_in_range, multihomed_sockets::BindIpAddrs,
48    },
49    solana_program_runtime::{
50        execution_budget::SVMTransactionExecutionBudget, invoke_context::InvokeContext,
51    },
52    solana_pubkey::Pubkey,
53    solana_rent::Rent,
54    solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig},
55    solana_rpc_client::{nonblocking, rpc_client::RpcClient},
56    solana_rpc_client_api::{
57        client_error::Error as RpcClientError, request::MAX_MULTIPLE_ACCOUNTS,
58    },
59    solana_runtime::{
60        bank_forks::BankForks, genesis_utils::create_genesis_config_with_leader_ex,
61        runtime_config::RuntimeConfig,
62    },
63    solana_sbpf::{elf::Executable, verifier::RequisiteVerifier},
64    solana_sdk_ids::address_lookup_table,
65    solana_signer::Signer,
66    solana_streamer::quic::DEFAULT_QUIC_ENDPOINTS,
67    solana_syscalls::create_program_runtime_environment,
68    solana_transaction::{Transaction, TransactionError},
69    solana_validator_exit::Exit,
70    std::{
71        collections::{HashMap, HashSet},
72        ffi::OsStr,
73        fmt::Display,
74        fs::{self, File, remove_dir_all},
75        io::Read,
76        net::{IpAddr, Ipv4Addr, SocketAddr},
77        num::{NonZero, NonZeroU64},
78        path::{Path, PathBuf},
79        str::FromStr,
80        sync::{Arc, RwLock},
81        time::Duration,
82    },
83    tokio::time::sleep,
84};
85
86#[derive(Clone)]
87pub struct AccountInfo<'a> {
88    pub address: Option<Pubkey>,
89    pub filename: &'a str,
90}
91
92#[derive(Clone)]
93pub struct UpgradeableProgramInfo {
94    pub program_id: Pubkey,
95    pub loader: Pubkey,
96    pub upgrade_authority: Pubkey,
97    pub program_path: PathBuf,
98}
99
100#[derive(Debug)]
101pub struct TestValidatorNodeConfig {
102    gossip_addr: SocketAddr,
103    port_range: PortRange,
104    bind_ip_addr: IpAddr,
105}
106
107impl Default for TestValidatorNodeConfig {
108    fn default() -> Self {
109        let bind_ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
110        #[cfg(not(debug_assertions))]
111        let port_range = solana_net_utils::VALIDATOR_PORT_RANGE;
112        #[cfg(debug_assertions)]
113        let port_range = solana_net_utils::sockets::localhost_port_range_for_tests();
114        Self {
115            gossip_addr: SocketAddr::new(bind_ip_addr, port_range.0),
116            port_range,
117            bind_ip_addr,
118        }
119    }
120}
121
122pub struct TestValidatorGenesis {
123    fee_rate_governor: FeeRateGovernor,
124    ledger_path: Option<PathBuf>,
125    tower_storage: Option<Arc<dyn TowerStorage>>,
126    pub rent: Rent,
127    rpc_config: JsonRpcConfig,
128    pubsub_config: PubSubConfig,
129    rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
130    warp_slot: Option<Slot>,
131    accounts: HashMap<Pubkey, AccountSharedData>,
132    upgradeable_programs: Vec<UpgradeableProgramInfo>,
133    ticks_per_slot: Option<u64>,
134    epoch_schedule: Option<EpochSchedule>,
135    inflation: Option<Inflation>,
136    node_config: TestValidatorNodeConfig,
137    pub validator_exit: Arc<RwLock<Exit>>,
138    pub start_progress: Arc<RwLock<ValidatorStartProgress>>,
139    pub authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
140    pub staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
141    pub max_ledger_shreds: Option<u64>,
142    pub max_genesis_archive_unpacked_size: Option<u64>,
143    pub geyser_plugin_config_files: Option<Vec<PathBuf>>,
144    pub enable_scheduler_bindings: bool,
145    deactivate_feature_set: HashSet<Pubkey>,
146    compute_unit_limit: Option<u64>,
147    pub log_messages_bytes_limit: Option<usize>,
148    pub transaction_account_lock_limit: Option<usize>,
149    pub geyser_plugin_manager: Arc<ArcSwap<GeyserPluginManager>>,
150    admin_rpc_service_post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
151}
152
153impl Default for TestValidatorGenesis {
154    fn default() -> Self {
155        // Default to Tower consensus to ensure proper converage pre-Alpenglow.
156        let deactivate_feature_set = [alpenglow::id()].into_iter().collect();
157        Self {
158            fee_rate_governor: FeeRateGovernor::default(),
159            ledger_path: Option::<PathBuf>::default(),
160            tower_storage: Option::<Arc<dyn TowerStorage>>::default(),
161            rent: Rent::default(),
162            rpc_config: JsonRpcConfig::default_for_test(),
163            pubsub_config: PubSubConfig::default_for_tests(),
164            rpc_ports: Option::<(u16, u16)>::default(),
165            warp_slot: Option::<Slot>::default(),
166            accounts: HashMap::<Pubkey, AccountSharedData>::default(),
167            upgradeable_programs: Vec::<UpgradeableProgramInfo>::default(),
168            ticks_per_slot: Option::<u64>::default(),
169            epoch_schedule: Option::<EpochSchedule>::default(),
170            inflation: Option::<Inflation>::default(),
171            node_config: TestValidatorNodeConfig::default(),
172            validator_exit: Arc::<RwLock<Exit>>::default(),
173            start_progress: Arc::<RwLock<ValidatorStartProgress>>::default(),
174            authorized_voter_keypairs: Arc::<RwLock<Vec<Arc<Keypair>>>>::default(),
175            staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
176            max_ledger_shreds: Option::<u64>::default(),
177            max_genesis_archive_unpacked_size: Option::<u64>::default(),
178            geyser_plugin_config_files: Option::<Vec<PathBuf>>::default(),
179            enable_scheduler_bindings: false,
180            deactivate_feature_set,
181            compute_unit_limit: Option::<u64>::default(),
182            log_messages_bytes_limit: Option::<usize>::default(),
183            transaction_account_lock_limit: Option::<usize>::default(),
184            geyser_plugin_manager: Arc::new(ArcSwap::new(Arc::new(GeyserPluginManager::default()))),
185            admin_rpc_service_post_init:
186                Arc::<RwLock<Option<AdminRpcRequestMetadataPostInit>>>::default(),
187        }
188    }
189}
190
191fn try_transform_program_data(
192    address: &Pubkey,
193    account: &mut AccountSharedData,
194) -> Result<(), String> {
195    if account.owner() == &solana_sdk_ids::bpf_loader_upgradeable::id() {
196        let programdata_offset = UpgradeableLoaderState::size_of_programdata_metadata();
197        let programdata_meta = account.data().get(0..programdata_offset).ok_or(format!(
198            "Failed to get upgradeable programdata data from {address}"
199        ))?;
200        // Ensure the account is a proper programdata account before
201        // attempting to serialize into it.
202        if let Ok(UpgradeableLoaderState::ProgramData {
203            upgrade_authority_address,
204            ..
205        }) = bincode::deserialize::<UpgradeableLoaderState>(programdata_meta)
206        {
207            // Serialize new programdata metadata into the resulting account,
208            // to overwrite the deployment slot to `0`.
209            bincode::serialize_into(
210                account.data_as_mut_slice(),
211                &UpgradeableLoaderState::ProgramData {
212                    slot: 0,
213                    upgrade_authority_address,
214                },
215            )
216            .map_err(|_| format!("Failed to write to upgradeable programdata account {address}"))
217        } else {
218            Err(format!(
219                "Failed to read upgradeable programdata account {address}"
220            ))
221        }
222    } else {
223        Err(format!("Account {address} not owned by upgradeable loader"))
224    }
225}
226
227impl TestValidatorGenesis {
228    /// Adds features to deactivate to a set, eliminating redundancies
229    /// during `initialize_ledger`, if member of the set is not a Feature
230    /// it will be silently ignored
231    pub fn deactivate_features(&mut self, deactivate_list: &[Pubkey]) -> &mut Self {
232        self.deactivate_feature_set.extend(deactivate_list);
233        self
234    }
235    pub fn ledger_path<P: Into<PathBuf>>(&mut self, ledger_path: P) -> &mut Self {
236        self.ledger_path = Some(ledger_path.into());
237        self
238    }
239
240    pub fn tower_storage(&mut self, tower_storage: Arc<dyn TowerStorage>) -> &mut Self {
241        self.tower_storage = Some(tower_storage);
242        self
243    }
244
245    /// Check if a given TestValidator ledger has already been initialized
246    pub fn ledger_exists(ledger_path: &Path) -> bool {
247        ledger_path.join("vote-account-keypair.json").exists()
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                .to_account_shared_data()
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    /// Sync only; calling from a tokio runtime will panic due to nested runtimes.
672    pub fn start_with_mint_address(
673        &self,
674        mint_address: Pubkey,
675        socket_addr_space: SocketAddrSpace,
676    ) -> Result<TestValidator, Box<dyn std::error::Error>> {
677        self.start_with_mint_address_and_geyser_plugin_rpc(mint_address, socket_addr_space, None)
678    }
679
680    /// Start a test validator with the address of the mint account that will receive tokens
681    /// created at genesis. Augments admin rpc service with dynamic geyser plugin manager if
682    /// the geyser plugin service is enabled at startup.
683    ///
684    /// Sync only; calling from a tokio runtime will panic due to nested runtimes.
685    pub fn start_with_mint_address_and_geyser_plugin_rpc(
686        &self,
687        mint_address: Pubkey,
688        socket_addr_space: SocketAddrSpace,
689        rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
690    ) -> Result<TestValidator, Box<dyn std::error::Error>> {
691        TestValidator::start(
692            mint_address,
693            self,
694            socket_addr_space,
695            rpc_to_plugin_manager_receiver,
696        )
697        .inspect(|test_validator| {
698            let runtime = tokio::runtime::Builder::new_current_thread()
699                .enable_io()
700                .enable_time()
701                .build()
702                .unwrap();
703            runtime.block_on(test_validator.wait_for_first_slot());
704        })
705    }
706
707    /// Start a test validator
708    ///
709    /// Returns a new `TestValidator` as well as the keypair for the mint account that will receive tokens
710    /// created at genesis.
711    ///
712    /// This function panics on initialization failure.
713    pub fn start(&self) -> (TestValidator, Keypair) {
714        self.start_with_socket_addr_space(SocketAddrSpace::new(/*allow_private_addr=*/ true))
715    }
716
717    /// Start a test validator with the given `SocketAddrSpace`
718    ///
719    /// Returns a new `TestValidator` as well as the keypair for the mint account that will receive tokens
720    /// created at genesis.
721    ///
722    /// This function panics on initialization failure.
723    /// Sync only; calling from a tokio runtime will panic due to nested runtimes.
724    pub fn start_with_socket_addr_space(
725        &self,
726        socket_addr_space: SocketAddrSpace,
727    ) -> (TestValidator, Keypair) {
728        let mint_keypair = Keypair::new();
729        self.start_with_mint_address(mint_keypair.pubkey(), socket_addr_space)
730            .inspect(|test_validator| {
731                let runtime = tokio::runtime::Builder::new_current_thread()
732                    .enable_io()
733                    .enable_time()
734                    .build()
735                    .unwrap();
736                let upgradeable_program_ids: Vec<&Pubkey> = self
737                    .upgradeable_programs
738                    .iter()
739                    .map(|p| &p.program_id)
740                    .collect();
741                runtime
742                    .block_on(test_validator.wait_for_upgradeable_programs_deployed(
743                        &upgradeable_program_ids,
744                        &mint_keypair,
745                    ))
746                    .unwrap_or_else(|err| {
747                        panic!("Failed to wait for programs to be deployed: {err:?}")
748                    });
749            })
750            .map(|test_validator| (test_validator, mint_keypair))
751            .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"))
752    }
753
754    /// Start a test validator with the address of the mint account that will receive tokens
755    /// created at genesis (async version).
756    pub async fn start_async_with_mint_address(
757        &self,
758        mint_keypair: &Keypair,
759        socket_addr_space: SocketAddrSpace,
760    ) -> Result<TestValidator, Box<dyn std::error::Error>> {
761        let test_validator =
762            TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space, None)?;
763        test_validator.wait_for_first_slot().await;
764        let upgradeable_program_ids: Vec<&Pubkey> = self
765            .upgradeable_programs
766            .iter()
767            .map(|p| &p.program_id)
768            .collect();
769        test_validator
770            .wait_for_upgradeable_programs_deployed(&upgradeable_program_ids, mint_keypair)
771            .await
772            .unwrap_or_else(|err| panic!("Failed to wait for programs to be deployed: {err:?}"));
773        Ok(test_validator)
774    }
775
776    pub async fn start_async(&self) -> (TestValidator, Keypair) {
777        self.start_async_with_socket_addr_space(SocketAddrSpace::new(
778            /*allow_private_addr=*/ true,
779        ))
780        .await
781    }
782
783    pub async fn start_async_with_socket_addr_space(
784        &self,
785        socket_addr_space: SocketAddrSpace,
786    ) -> (TestValidator, Keypair) {
787        let mint_keypair = Keypair::new();
788        let test_validator = self
789            .start_async_with_mint_address(&mint_keypair, socket_addr_space)
790            .await
791            .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"));
792        (test_validator, mint_keypair)
793    }
794}
795
796pub struct TestValidator {
797    ledger_path: PathBuf,
798    preserve_ledger: bool,
799    rpc_pubsub_url: String,
800    rpc_url: String,
801    tpu_quic: SocketAddr,
802    gossip: SocketAddr,
803    validator: Option<Validator>,
804    vote_account_address: Pubkey,
805}
806
807impl TestValidator {
808    /// Create a configured genesis and start validator
809    /// Sync only; calling from a tokio runtime will panic due to nested runtimes.
810    pub fn start_with_config(
811        mint_address: Pubkey,
812        faucet_addr: Option<SocketAddr>,
813        socket_addr_space: SocketAddrSpace,
814    ) -> Self {
815        TestValidatorGenesis::default()
816            .rent(Rent {
817                lamports_per_byte: 1,
818                ..Rent::default()
819            })
820            .faucet_addr(faucet_addr)
821            .start_with_mint_address(mint_address, socket_addr_space)
822            .expect("validator start failed")
823    }
824
825    /// Create a configured genesis and start validator (async version)
826    pub async fn async_start_with_config(
827        mint_keypair: &Keypair,
828        faucet_addr: Option<SocketAddr>,
829        socket_addr_space: SocketAddrSpace,
830    ) -> Self {
831        TestValidatorGenesis::default()
832            .rent(Rent {
833                lamports_per_byte: 1,
834                ..Rent::default()
835            })
836            .faucet_addr(faucet_addr)
837            .start_async_with_mint_address(mint_keypair, socket_addr_space)
838            .await
839            .expect("validator start failed")
840    }
841
842    /// Initialize the ledger directory
843    ///
844    /// If `ledger_path` is `None`, a temporary ledger will be created.  Otherwise the ledger will
845    /// be initialized in the provided directory if it doesn't already exist.
846    ///
847    /// Returns the path to the ledger directory.
848    fn initialize_ledger(
849        mint_address: Pubkey,
850        config: &TestValidatorGenesis,
851    ) -> Result<PathBuf, Box<dyn std::error::Error>> {
852        let validator_identity = Keypair::new();
853        let validator_vote_account = Keypair::new();
854        let validator_stake_account = Keypair::new();
855        let validator_identity_lamports = 500 * LAMPORTS_PER_SOL;
856        let validator_stake_lamports = 1_000_000 * LAMPORTS_PER_SOL;
857        let mint_lamports = 500_000_000 * LAMPORTS_PER_SOL;
858
859        // Only activate features which are not explicitly deactivated.
860        let mut feature_set = FeatureSet::all_enabled();
861        for feature in &config.deactivate_feature_set {
862            if FEATURE_NAMES.contains_key(feature) {
863                feature_set.deactivate(feature);
864                info!("Feature for {feature:?} deactivated");
865            } else {
866                warn!("Feature {feature:?} set for deactivation is not a known Feature public key",)
867            }
868        }
869
870        let runtime_features = feature_set.runtime_features();
871        let program_runtime_environment = create_program_runtime_environment(
872            &runtime_features,
873            &SVMTransactionExecutionBudget::new_with_defaults(
874                runtime_features.raise_cpi_nesting_limit_to_8,
875            ),
876            true,
877            false,
878        )?;
879
880        let mut accounts = config.accounts.clone();
881        for (address, account) in solana_program_binaries::spl_programs(&config.rent) {
882            accounts.entry(address).or_insert(account);
883        }
884        for (address, account) in
885            solana_program_binaries::core_bpf_programs(&config.rent, |feature_id| {
886                feature_set.is_active(feature_id)
887            })
888        {
889            accounts.entry(address).or_insert(account);
890        }
891        for upgradeable_program in &config.upgradeable_programs {
892            let data = solana_program_test::read_file(&upgradeable_program.program_path);
893            let executable = Executable::<InvokeContext>::from_elf(
894                &data,
895                Arc::clone(&*program_runtime_environment),
896            )
897            .map_err(|err| format!("ELF error: {err}"))?;
898            executable
899                .verify::<RequisiteVerifier>()
900                .map_err(|err| format!("ELF error: {err}"))?;
901
902            let (programdata_address, _) = Pubkey::find_program_address(
903                &[upgradeable_program.program_id.as_ref()],
904                &upgradeable_program.loader,
905            );
906            let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
907                slot: 0,
908                upgrade_authority_address: Some(upgradeable_program.upgrade_authority),
909            })
910            .unwrap();
911            program_data.extend_from_slice(&data);
912            accounts.insert(
913                programdata_address,
914                AccountSharedData::from(Account {
915                    lamports: Rent::default().minimum_balance(program_data.len()).max(1),
916                    data: program_data,
917                    owner: upgradeable_program.loader,
918                    executable: false,
919                    rent_epoch: 0,
920                }),
921            );
922
923            let data = bincode::serialize(&UpgradeableLoaderState::Program {
924                programdata_address,
925            })
926            .unwrap();
927            accounts.insert(
928                upgradeable_program.program_id,
929                AccountSharedData::from(Account {
930                    lamports: Rent::default().minimum_balance(data.len()).max(1),
931                    data,
932                    owner: upgradeable_program.loader,
933                    executable: true,
934                    rent_epoch: 0,
935                }),
936            );
937        }
938
939        let mut genesis_config = create_genesis_config_with_leader_ex(
940            mint_lamports,
941            &mint_address,
942            &validator_identity.pubkey(),
943            &validator_vote_account.pubkey(),
944            &validator_stake_account.pubkey(),
945            None,
946            validator_stake_lamports,
947            validator_identity_lamports,
948            config.fee_rate_governor.clone(),
949            config.rent.clone(),
950            solana_cluster_type::ClusterType::Development,
951            &feature_set,
952            accounts.into_iter().collect(),
953        );
954        genesis_config.epoch_schedule = config
955            .epoch_schedule
956            .as_ref()
957            .cloned()
958            .unwrap_or_else(EpochSchedule::without_warmup);
959
960        if let Some(ticks_per_slot) = config.ticks_per_slot {
961            genesis_config.ticks_per_slot = ticks_per_slot;
962        }
963
964        if let Some(inflation) = config.inflation {
965            genesis_config.inflation = inflation;
966        }
967
968        let ledger_path = match &config.ledger_path {
969            None => create_new_tmp_ledger!(&genesis_config).0,
970            Some(ledger_path) => {
971                if TestValidatorGenesis::ledger_exists(ledger_path) {
972                    return Ok(ledger_path.to_path_buf());
973                }
974
975                let _ = create_new_ledger(
976                    ledger_path,
977                    &genesis_config,
978                    config
979                        .max_genesis_archive_unpacked_size
980                        .unwrap_or(MAX_GENESIS_ARCHIVE_UNPACKED_SIZE),
981                    LedgerColumnOptions::default(),
982                )
983                .map_err(|err| {
984                    format!(
985                        "Failed to create ledger at {}: {}",
986                        ledger_path.display(),
987                        err
988                    )
989                })?;
990                ledger_path.to_path_buf()
991            }
992        };
993
994        write_keypair_file(
995            &validator_identity,
996            ledger_path.join("validator-keypair.json").to_str().unwrap(),
997        )?;
998
999        write_keypair_file(
1000            &validator_stake_account,
1001            ledger_path
1002                .join("stake-account-keypair.json")
1003                .to_str()
1004                .unwrap(),
1005        )?;
1006
1007        // `ledger_exists` should fail until the vote account keypair is written
1008        assert!(!TestValidatorGenesis::ledger_exists(&ledger_path));
1009
1010        write_keypair_file(
1011            &validator_vote_account,
1012            ledger_path
1013                .join("vote-account-keypair.json")
1014                .to_str()
1015                .unwrap(),
1016        )?;
1017
1018        Ok(ledger_path)
1019    }
1020
1021    /// Starts a TestValidator at the provided ledger directory
1022    fn start(
1023        mint_address: Pubkey,
1024        config: &TestValidatorGenesis,
1025        socket_addr_space: SocketAddrSpace,
1026        rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
1027    ) -> Result<Self, Box<dyn std::error::Error>> {
1028        let preserve_ledger = config.ledger_path.is_some();
1029        let ledger_path = TestValidator::initialize_ledger(mint_address, config)?;
1030
1031        let validator_identity =
1032            read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
1033        let validator_vote_account = read_keypair_file(
1034            ledger_path
1035                .join("vote-account-keypair.json")
1036                .to_str()
1037                .unwrap(),
1038        )?;
1039        let node = {
1040            let bind_ip_addr = config.node_config.bind_ip_addr;
1041            let validator_node_config = NodeConfig {
1042                bind_ip_addrs: BindIpAddrs::new(vec![bind_ip_addr])?,
1043                gossip_port: config.node_config.gossip_addr.port(),
1044                port_range: config.node_config.port_range,
1045                advertised_ip: bind_ip_addr,
1046                public_tvu_addr: None,
1047                public_tpu_addr: None,
1048                public_tpu_forwards_addr: None,
1049                num_tvu_receive_sockets: NonZero::new(1).unwrap(),
1050                num_tvu_retransmit_sockets: NonZero::new(1).unwrap(),
1051                num_quic_endpoints: NonZero::new(DEFAULT_QUIC_ENDPOINTS)
1052                    .expect("Number of QUIC endpoints can not be zero"),
1053            };
1054            let mut node =
1055                Node::new_with_external_ip(&validator_identity.pubkey(), validator_node_config);
1056            let (rpc, rpc_pubsub) = config.rpc_ports.unwrap_or_else(|| {
1057                let rpc_ports: [u16; 2] =
1058                    find_available_ports_in_range(bind_ip_addr, config.node_config.port_range)
1059                        .unwrap();
1060                (rpc_ports[0], rpc_ports[1])
1061            });
1062            node.info.set_rpc((bind_ip_addr, rpc)).unwrap();
1063            node.info
1064                .set_rpc_pubsub((bind_ip_addr, rpc_pubsub))
1065                .unwrap();
1066            node
1067        };
1068
1069        let vote_account_address = validator_vote_account.pubkey();
1070        let rpc_url = format!("http://{}", node.info.rpc().unwrap());
1071        let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub().unwrap());
1072        let tpu_quic = node.info.tpu(Protocol::QUIC).unwrap();
1073        let gossip = node.info.gossip().unwrap();
1074
1075        {
1076            let mut authorized_voter_keypairs: std::sync::RwLockWriteGuard<'_, Vec<Arc<Keypair>>> =
1077                config.authorized_voter_keypairs.write().unwrap();
1078            if !authorized_voter_keypairs
1079                .iter()
1080                .any(|x| x.pubkey() == vote_account_address)
1081            {
1082                authorized_voter_keypairs.push(Arc::new(validator_vote_account))
1083            }
1084        }
1085
1086        let accounts_db_config = AccountsDbConfig {
1087            index: Some(AccountsIndexConfig::default()),
1088            account_indexes: Some(config.rpc_config.account_indexes.clone()),
1089            scan_filter_for_shrinking: ScanFilter::All,
1090            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1091        };
1092
1093        let runtime_config = RuntimeConfig {
1094            compute_budget: config
1095                .compute_unit_limit
1096                .map(|compute_unit_limit| ComputeBudget {
1097                    compute_unit_limit,
1098                    ..ComputeBudget::new_with_defaults(
1099                        !config
1100                            .deactivate_feature_set
1101                            .contains(&raise_cpi_nesting_limit_to_8::id()),
1102                    )
1103                }),
1104            log_messages_bytes_limit: config.log_messages_bytes_limit,
1105            transaction_account_lock_limit: config.transaction_account_lock_limit,
1106        };
1107
1108        let mut validator_config = ValidatorConfig {
1109            on_start_geyser_plugin_config_files: config.geyser_plugin_config_files.clone(),
1110            rpc_addrs: Some((
1111                SocketAddr::new(
1112                    IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1113                    node.info.rpc().unwrap().port(),
1114                ),
1115                SocketAddr::new(
1116                    IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1117                    node.info.rpc_pubsub().unwrap().port(),
1118                ),
1119            )),
1120            rpc_config: config.rpc_config.clone(),
1121            pubsub_config: config.pubsub_config.clone(),
1122            account_paths: vec![
1123                create_accounts_run_and_snapshot_dirs(ledger_path.join("accounts"))
1124                    .unwrap()
1125                    .0,
1126            ],
1127            run_verification: false, // Skip PoH verification of ledger on startup for speed
1128            snapshot_config: SnapshotConfig {
1129                full_snapshot_archive_interval: SnapshotInterval::Slots(
1130                    NonZeroU64::new(100).unwrap(),
1131                ),
1132                incremental_snapshot_archive_interval: SnapshotInterval::Disabled,
1133                bank_snapshots_dir: ledger_path.join(BANK_SNAPSHOTS_DIR),
1134                full_snapshot_archives_dir: ledger_path.to_path_buf(),
1135                incremental_snapshot_archives_dir: ledger_path.to_path_buf(),
1136                use_registered_io_uring_buffers: false,
1137                use_direct_io: false,
1138                ..SnapshotConfig::default()
1139            },
1140            warp_slot: config.warp_slot,
1141            validator_exit: config.validator_exit.clone(),
1142            max_ledger_shreds: config.max_ledger_shreds,
1143            no_wait_for_vote_to_start_leader: true,
1144            staked_nodes_overrides: config.staked_nodes_overrides.clone(),
1145            accounts_db_config,
1146            runtime_config,
1147            enable_scheduler_bindings: config.enable_scheduler_bindings,
1148            ..ValidatorConfig::default_for_test()
1149        };
1150        if let Some(ref tower_storage) = config.tower_storage {
1151            validator_config.tower_storage = tower_storage.clone();
1152        }
1153
1154        let validator = Some(Validator::new(
1155            node,
1156            Arc::new(validator_identity),
1157            &ledger_path,
1158            &vote_account_address,
1159            config.authorized_voter_keypairs.clone(),
1160            vec![],
1161            &validator_config,
1162            rpc_to_plugin_manager_receiver,
1163            config.start_progress.clone(),
1164            socket_addr_space,
1165            ValidatorTpuConfig::new_for_tests(),
1166            config.admin_rpc_service_post_init.clone(),
1167            None,
1168        )?);
1169
1170        let test_validator = TestValidator {
1171            ledger_path,
1172            preserve_ledger,
1173            rpc_pubsub_url,
1174            rpc_url,
1175            tpu_quic,
1176            gossip,
1177            validator,
1178            vote_account_address,
1179        };
1180        Ok(test_validator)
1181    }
1182
1183    /// Delay until the validator has produced its first slot after startup.
1184    async fn wait_for_first_slot(&self) {
1185        let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1186            self.rpc_url.clone(),
1187            CommitmentConfig::processed(),
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 first slot {num_tries:?}...");
1197            match rpc_client.get_slot().await {
1198                Ok(slot) => {
1199                    if slot > 0 {
1200                        break;
1201                    }
1202                }
1203                Err(err) => {
1204                    warn!("get_slot() failed: {err:?}");
1205                    break;
1206                }
1207            }
1208            sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1209        }
1210    }
1211
1212    /// programs added to genesis ain't immediately usable. Actively check "Program
1213    /// is not deployed" error for their availibility.
1214    ///
1215    /// Returns `TransactionError::AccountNotFound` if the payer account is not funded.
1216    /// The caller is responsible for ensuring the payer account has sufficient funds.
1217    async fn wait_for_upgradeable_programs_deployed(
1218        &self,
1219        upgradeable_programs: &[&Pubkey],
1220        payer: &Keypair,
1221    ) -> Result<(), RpcClientError> {
1222        let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1223            self.rpc_url.clone(),
1224            CommitmentConfig::processed(),
1225        );
1226
1227        let mut deployed = vec![false; upgradeable_programs.len()];
1228        const MAX_ATTEMPTS: u64 = 10;
1229
1230        for attempt in 1..=MAX_ATTEMPTS {
1231            let blockhash = rpc_client.get_latest_blockhash().await.unwrap();
1232            for (program_id, is_deployed) in upgradeable_programs.iter().zip(deployed.iter_mut()) {
1233                if *is_deployed {
1234                    continue;
1235                }
1236
1237                let transaction = Transaction::new_signed_with_payer(
1238                    &[Instruction {
1239                        program_id: **program_id,
1240                        accounts: vec![],
1241                        data: vec![],
1242                    }],
1243                    Some(&payer.pubkey()),
1244                    &[&payer],
1245                    blockhash,
1246                );
1247                match rpc_client.simulate_transaction(&transaction).await {
1248                    Ok(response) => {
1249                        if let Some(e) = response.value.err {
1250                            let err_string = format!("{e:?}");
1251                            if err_string.contains("Program is not deployed") {
1252                                debug!("{program_id:?} - not deployed");
1253                            } else if err_string.contains("AccountNotFound") {
1254                                // Payer account not funded - this is a caller error
1255                                return Err(RpcClientError::from(
1256                                    TransactionError::AccountNotFound,
1257                                ));
1258                            } else {
1259                                // Assuming all 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                        } else {
1265                            *is_deployed = true;
1266                        }
1267                    }
1268                    Err(e) => {
1269                        warn!("Failed to simulate transaction: {e:?}");
1270                        // Error if we're at final attempt - flakiness is tolerated up to MAX_ATTEMPTS
1271                        if attempt == MAX_ATTEMPTS {
1272                            return Err(e);
1273                        }
1274                    }
1275                }
1276            }
1277            if deployed.iter().all(|&deployed| deployed) {
1278                return Ok(());
1279            }
1280
1281            println!("Waiting for programs to be fully deployed {attempt} ...");
1282            sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1283        }
1284        panic!("Timeout waiting for program to become usable");
1285    }
1286
1287    /// Return the validator's TPU QUIC address
1288    pub fn tpu_quic(&self) -> &SocketAddr {
1289        &self.tpu_quic
1290    }
1291
1292    /// Return the validator's Gossip address
1293    pub fn gossip(&self) -> &SocketAddr {
1294        &self.gossip
1295    }
1296
1297    /// Return the validator's JSON RPC URL
1298    pub fn rpc_url(&self) -> String {
1299        self.rpc_url.clone()
1300    }
1301
1302    /// Return the validator's JSON RPC PubSub URL
1303    pub fn rpc_pubsub_url(&self) -> String {
1304        self.rpc_pubsub_url.clone()
1305    }
1306
1307    /// Return the validator's vote account address
1308    pub fn vote_account_address(&self) -> Pubkey {
1309        self.vote_account_address
1310    }
1311
1312    /// Return an RpcClient for the validator.
1313    pub fn get_rpc_client(&self) -> RpcClient {
1314        RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed())
1315    }
1316
1317    /// Return a nonblocking RpcClient for the validator.
1318    pub fn get_async_rpc_client(&self) -> nonblocking::rpc_client::RpcClient {
1319        nonblocking::rpc_client::RpcClient::new_with_commitment(
1320            self.rpc_url.clone(),
1321            CommitmentConfig::processed(),
1322        )
1323    }
1324
1325    pub fn join(mut self) {
1326        if let Some(validator) = self.validator.take() {
1327            validator.join();
1328        }
1329    }
1330
1331    pub fn cluster_info(&self) -> Arc<ClusterInfo> {
1332        self.validator.as_ref().unwrap().cluster_info.clone()
1333    }
1334
1335    pub fn bank_forks(&self) -> Arc<RwLock<BankForks>> {
1336        self.validator.as_ref().unwrap().bank_forks.clone()
1337    }
1338
1339    pub fn repair_whitelist(&self) -> Arc<RwLock<HashSet<Pubkey>>> {
1340        Arc::new(RwLock::new(HashSet::default()))
1341    }
1342}
1343
1344impl Drop for TestValidator {
1345    fn drop(&mut self) {
1346        if let Some(validator) = self.validator.take() {
1347            validator.close();
1348        }
1349        if !self.preserve_ledger {
1350            remove_dir_all(&self.ledger_path).unwrap_or_else(|err| {
1351                panic!(
1352                    "Failed to remove ledger directory {}: {}",
1353                    self.ledger_path.display(),
1354                    err
1355                )
1356            });
1357        }
1358    }
1359}
1360
1361#[cfg(test)]
1362mod test {
1363    use {super::*, solana_feature_gate_interface::Feature};
1364
1365    #[test]
1366    fn get_health() {
1367        let (test_validator, _payer) = TestValidatorGenesis::default().start();
1368        let rpc_client = test_validator.get_rpc_client();
1369        rpc_client.get_health().expect("health");
1370    }
1371
1372    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1373    async fn nonblocking_get_health() {
1374        let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1375        let rpc_client = test_validator.get_async_rpc_client();
1376        rpc_client.get_health().await.expect("health");
1377    }
1378
1379    #[test]
1380    fn test_upgradeable_program_deploayment() {
1381        let program_id = Pubkey::new_unique();
1382        let (test_validator, payer) = TestValidatorGenesis::default()
1383            .add_program("../programs/bpf-loader-tests/noop", program_id)
1384            .start();
1385        let rpc_client = test_validator.get_rpc_client();
1386
1387        let blockhash = rpc_client.get_latest_blockhash().unwrap();
1388        let transaction = Transaction::new_signed_with_payer(
1389            &[Instruction {
1390                program_id,
1391                accounts: vec![],
1392                data: vec![],
1393            }],
1394            Some(&payer.pubkey()),
1395            &[&payer],
1396            blockhash,
1397        );
1398
1399        assert!(
1400            rpc_client
1401                .send_and_confirm_transaction(&transaction)
1402                .is_ok()
1403        );
1404    }
1405
1406    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1407    async fn test_nonblocking_upgradeable_program_deploayment() {
1408        let program_id = Pubkey::new_unique();
1409        let (test_validator, payer) = TestValidatorGenesis::default()
1410            .add_program("../programs/bpf-loader-tests/noop", program_id)
1411            .start_async()
1412            .await;
1413        let rpc_client = test_validator.get_async_rpc_client();
1414
1415        let blockhash = rpc_client.get_latest_blockhash().await.unwrap();
1416        let transaction = Transaction::new_signed_with_payer(
1417            &[Instruction {
1418                program_id,
1419                accounts: vec![],
1420                data: vec![],
1421            }],
1422            Some(&payer.pubkey()),
1423            &[&payer],
1424            blockhash,
1425        );
1426
1427        assert!(
1428            rpc_client
1429                .send_and_confirm_transaction(&transaction)
1430                .await
1431                .is_ok()
1432        );
1433    }
1434
1435    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1436    #[should_panic]
1437    async fn document_tokio_panic() {
1438        // `start()` blows up when run within tokio
1439        let (_test_validator, _payer) = TestValidatorGenesis::default().start();
1440    }
1441
1442    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1443    async fn test_deactivate_features() {
1444        let mut control = FeatureSet::default().inactive().clone();
1445        let mut deactivate_features = Vec::new();
1446        [
1447            agave_feature_set::deprecate_rewards_sysvar::id(),
1448            agave_feature_set::disable_fees_sysvar::id(),
1449            alpenglow::id(),
1450            agave_feature_set::bls_pubkey_management_in_vote_account::id(),
1451            agave_feature_set::vote_account_initialize_v2::id(),
1452            agave_feature_set::validator_admission_ticket::id(),
1453        ]
1454        .into_iter()
1455        .for_each(|feature| {
1456            control.remove(&feature);
1457            deactivate_features.push(feature);
1458        });
1459
1460        // Convert to `Vec` so we can get a slice.
1461        let control: Vec<Pubkey> = control.into_iter().collect();
1462
1463        let (test_validator, _payer) = TestValidatorGenesis::default()
1464            .deactivate_features(&deactivate_features)
1465            .start_async()
1466            .await;
1467
1468        let rpc_client = test_validator.get_async_rpc_client();
1469
1470        // Our deactivated features should be inactive.
1471        let inactive_feature_accounts = rpc_client
1472            .get_multiple_accounts(&deactivate_features)
1473            .await
1474            .unwrap();
1475        for f in inactive_feature_accounts {
1476            assert!(f.is_none());
1477        }
1478
1479        // Everything else should be active.
1480        for chunk in control.chunks(100) {
1481            let active_feature_accounts = rpc_client.get_multiple_accounts(chunk).await.unwrap();
1482            for f in active_feature_accounts {
1483                let account = f.unwrap(); // Should be `Some`.
1484                let feature_state: Feature = bincode::deserialize(account.data()).unwrap();
1485                assert!(feature_state.activated_at.is_some());
1486            }
1487        }
1488    }
1489
1490    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1491    async fn test_override_feature_account() {
1492        let with_deactivate_flag = agave_feature_set::deprecate_rewards_sysvar::id();
1493        let without_deactivate_flag = agave_feature_set::disable_fees_sysvar::id();
1494
1495        let owner = Pubkey::new_unique();
1496        let account = || AccountSharedData::new(100_000, 0, &owner);
1497
1498        let (test_validator, _payer) = TestValidatorGenesis::default()
1499            .deactivate_features(&[with_deactivate_flag]) // Just deactivate one feature.
1500            .add_accounts([
1501                (with_deactivate_flag, account()), // But add both accounts.
1502                (without_deactivate_flag, account()),
1503            ])
1504            .start_async()
1505            .await;
1506
1507        let rpc_client = test_validator.get_async_rpc_client();
1508
1509        let our_accounts = rpc_client
1510            .get_multiple_accounts(&[with_deactivate_flag, without_deactivate_flag])
1511            .await
1512            .unwrap();
1513
1514        // The first one, where we provided `--deactivate-feature`, should be
1515        // the account we provided.
1516        let overridden_account = our_accounts[0].as_ref().unwrap();
1517        assert_eq!(overridden_account.lamports, 100_000);
1518        assert_eq!(overridden_account.data.len(), 0);
1519        assert_eq!(overridden_account.owner, owner);
1520
1521        // The second one should be a feature account.
1522        let feature_account = our_accounts[1].as_ref().unwrap();
1523        assert_eq!(feature_account.owner, solana_sdk_ids::feature::id());
1524        let feature_state: Feature = bincode::deserialize(feature_account.data()).unwrap();
1525        assert!(feature_state.activated_at.is_some());
1526    }
1527
1528    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1529    async fn test_core_bpf_programs() {
1530        let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1531
1532        let rpc_client = test_validator.get_async_rpc_client();
1533
1534        let fetched_programs = rpc_client
1535            .get_multiple_accounts(&[
1536                solana_sdk_ids::address_lookup_table::id(),
1537                solana_sdk_ids::config::id(),
1538                solana_sdk_ids::feature::id(),
1539                solana_sdk_ids::stake::id(),
1540            ])
1541            .await
1542            .unwrap();
1543
1544        // Address lookup table is a BPF program.
1545        let account = fetched_programs[0].as_ref().unwrap();
1546        assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1547        assert!(account.executable);
1548
1549        // Config is a BPF program.
1550        let account = fetched_programs[1].as_ref().unwrap();
1551        assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1552        assert!(account.executable);
1553
1554        // Feature Gate is a BPF program.
1555        let account = fetched_programs[2].as_ref().unwrap();
1556        assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1557        assert!(account.executable);
1558
1559        // Stake is a BPF program.
1560        let account = fetched_programs[3].as_ref().unwrap();
1561        assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
1562        assert!(account.executable);
1563    }
1564
1565    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1566    async fn test_wait_for_program_with_unfunded_payer() {
1567        let program_id = Pubkey::new_unique();
1568        let (test_validator, _mint_keypair) = TestValidatorGenesis::default()
1569            .add_program("../programs/bpf-loader-tests/noop", program_id)
1570            .start_async()
1571            .await;
1572
1573        // Create an unfunded payer keypair
1574        let unfunded_payer = Keypair::new();
1575
1576        // Call wait_for_upgradeable_programs_deployed with unfunded payer
1577        let result = test_validator
1578            .wait_for_upgradeable_programs_deployed(&[&program_id], &unfunded_payer)
1579            .await;
1580
1581        // Verify it returns AccountNotFound error
1582        let err = result.unwrap_err();
1583        assert!(matches!(
1584            *err.kind,
1585            solana_rpc_client_api::client_error::ErrorKind::TransactionError(
1586                TransactionError::AccountNotFound
1587            )
1588        ));
1589    }
1590}