Skip to main content

solana_test_validator/
lib.rs

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