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