solana_test_validator/
lib.rs

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