Skip to main content

solana_test_validator/
lib.rs

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