solana_test_validator/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2use {
3    base64::{prelude::BASE64_STANDARD, Engine},
4    crossbeam_channel::Receiver,
5    log::*,
6    solana_accounts_db::{
7        accounts_db::AccountsDbConfig, accounts_index::AccountsIndexConfig,
8        hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
9        utils::create_accounts_run_and_snapshot_dirs,
10    },
11    solana_cli_output::CliAccount,
12    solana_compute_budget::compute_budget::ComputeBudget,
13    solana_core::{
14        admin_rpc_post_init::AdminRpcRequestMetadataPostInit,
15        consensus::tower_storage::TowerStorage,
16        validator::{Validator, ValidatorConfig, ValidatorStartProgress},
17    },
18    solana_feature_set::FEATURE_NAMES,
19    solana_geyser_plugin_manager::{
20        geyser_plugin_manager::GeyserPluginManager, GeyserPluginManagerRequest,
21    },
22    solana_gossip::{
23        cluster_info::{ClusterInfo, Node},
24        contact_info::Protocol,
25        gossip_service::discover_cluster,
26        socketaddr,
27    },
28    solana_ledger::{
29        blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions,
30        create_new_tmp_ledger,
31    },
32    solana_net_utils::PortRange,
33    solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig},
34    solana_rpc_client::{nonblocking, rpc_client::RpcClient},
35    solana_rpc_client_api::request::MAX_MULTIPLE_ACCOUNTS,
36    solana_runtime::{
37        bank_forks::BankForks, genesis_utils::create_genesis_config_with_leader_ex,
38        runtime_config::RuntimeConfig, snapshot_config::SnapshotConfig,
39    },
40    solana_sdk::{
41        account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
42        bpf_loader_upgradeable::UpgradeableLoaderState,
43        clock::{Slot, DEFAULT_MS_PER_SLOT},
44        commitment_config::CommitmentConfig,
45        epoch_schedule::EpochSchedule,
46        exit::Exit,
47        fee_calculator::FeeRateGovernor,
48        instruction::{AccountMeta, Instruction},
49        message::Message,
50        native_token::sol_to_lamports,
51        pubkey::Pubkey,
52        rent::Rent,
53        signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
54    },
55    solana_streamer::socket::SocketAddrSpace,
56    solana_tpu_client::tpu_client::{
57        DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
58    },
59    std::{
60        collections::{HashMap, HashSet},
61        ffi::OsStr,
62        fmt::Display,
63        fs::{self, remove_dir_all, File},
64        io::Read,
65        net::{IpAddr, Ipv4Addr, SocketAddr},
66        path::{Path, PathBuf},
67        str::FromStr,
68        sync::{Arc, RwLock},
69        time::Duration,
70    },
71    tokio::time::sleep,
72};
73
74#[derive(Clone)]
75pub struct AccountInfo<'a> {
76    pub address: Option<Pubkey>,
77    pub filename: &'a str,
78}
79
80#[derive(Clone)]
81pub struct UpgradeableProgramInfo {
82    pub program_id: Pubkey,
83    pub loader: Pubkey,
84    pub upgrade_authority: Pubkey,
85    pub program_path: PathBuf,
86}
87
88#[derive(Debug)]
89pub struct TestValidatorNodeConfig {
90    gossip_addr: SocketAddr,
91    port_range: PortRange,
92    bind_ip_addr: IpAddr,
93}
94
95impl Default for TestValidatorNodeConfig {
96    fn default() -> Self {
97        const MIN_PORT_RANGE: u16 = 1024;
98        const MAX_PORT_RANGE: u16 = 65535;
99
100        let bind_ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
101        let port_range = (MIN_PORT_RANGE, MAX_PORT_RANGE);
102
103        Self {
104            gossip_addr: socketaddr!(Ipv4Addr::LOCALHOST, 0),
105            port_range,
106            bind_ip_addr,
107        }
108    }
109}
110
111pub struct TestValidatorGenesis {
112    fee_rate_governor: FeeRateGovernor,
113    ledger_path: Option<PathBuf>,
114    tower_storage: Option<Arc<dyn TowerStorage>>,
115    pub rent: Rent,
116    rpc_config: JsonRpcConfig,
117    pubsub_config: PubSubConfig,
118    rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
119    warp_slot: Option<Slot>,
120    accounts: HashMap<Pubkey, AccountSharedData>,
121    upgradeable_programs: Vec<UpgradeableProgramInfo>,
122    ticks_per_slot: Option<u64>,
123    epoch_schedule: Option<EpochSchedule>,
124    node_config: TestValidatorNodeConfig,
125    pub validator_exit: Arc<RwLock<Exit>>,
126    pub start_progress: Arc<RwLock<ValidatorStartProgress>>,
127    pub authorized_voter_keypairs: Arc<RwLock<Vec<Arc<Keypair>>>>,
128    pub staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
129    pub max_ledger_shreds: Option<u64>,
130    pub max_genesis_archive_unpacked_size: Option<u64>,
131    pub geyser_plugin_config_files: Option<Vec<PathBuf>>,
132    deactivate_feature_set: HashSet<Pubkey>,
133    compute_unit_limit: Option<u64>,
134    pub log_messages_bytes_limit: Option<usize>,
135    pub transaction_account_lock_limit: Option<usize>,
136    pub tpu_enable_udp: bool,
137    pub geyser_plugin_manager: Arc<RwLock<GeyserPluginManager>>,
138    admin_rpc_service_post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
139}
140
141impl Default for TestValidatorGenesis {
142    fn default() -> Self {
143        Self {
144            fee_rate_governor: FeeRateGovernor::default(),
145            ledger_path: Option::<PathBuf>::default(),
146            tower_storage: Option::<Arc<dyn TowerStorage>>::default(),
147            rent: Rent::default(),
148            rpc_config: JsonRpcConfig::default_for_test(),
149            pubsub_config: PubSubConfig::default(),
150            rpc_ports: Option::<(u16, u16)>::default(),
151            warp_slot: Option::<Slot>::default(),
152            accounts: HashMap::<Pubkey, AccountSharedData>::default(),
153            upgradeable_programs: Vec::<UpgradeableProgramInfo>::default(),
154            ticks_per_slot: Option::<u64>::default(),
155            epoch_schedule: Option::<EpochSchedule>::default(),
156            node_config: TestValidatorNodeConfig::default(),
157            validator_exit: Arc::<RwLock<Exit>>::default(),
158            start_progress: Arc::<RwLock<ValidatorStartProgress>>::default(),
159            authorized_voter_keypairs: Arc::<RwLock<Vec<Arc<Keypair>>>>::default(),
160            staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
161            max_ledger_shreds: Option::<u64>::default(),
162            max_genesis_archive_unpacked_size: Option::<u64>::default(),
163            geyser_plugin_config_files: Option::<Vec<PathBuf>>::default(),
164            deactivate_feature_set: HashSet::<Pubkey>::default(),
165            compute_unit_limit: Option::<u64>::default(),
166            log_messages_bytes_limit: Option::<usize>::default(),
167            transaction_account_lock_limit: Option::<usize>::default(),
168            tpu_enable_udp: DEFAULT_TPU_ENABLE_UDP,
169            geyser_plugin_manager: Arc::new(RwLock::new(GeyserPluginManager::new())),
170            admin_rpc_service_post_init:
171                Arc::<RwLock<Option<AdminRpcRequestMetadataPostInit>>>::default(),
172        }
173    }
174}
175
176fn try_transform_program_data(
177    address: &Pubkey,
178    account: &mut AccountSharedData,
179) -> Result<(), String> {
180    if account.owner() == &solana_sdk::bpf_loader_upgradeable::id() {
181        let programdata_offset = UpgradeableLoaderState::size_of_programdata_metadata();
182        let programdata_meta = account.data().get(0..programdata_offset).ok_or(format!(
183            "Failed to get upgradeable programdata data from {address}"
184        ))?;
185        // Ensure the account is a proper programdata account before
186        // attempting to serialize into it.
187        if let Ok(UpgradeableLoaderState::ProgramData {
188            upgrade_authority_address,
189            ..
190        }) = bincode::deserialize::<UpgradeableLoaderState>(programdata_meta)
191        {
192            // Serialize new programdata metadata into the resulting account,
193            // to overwrite the deployment slot to `0`.
194            bincode::serialize_into(
195                account.data_as_mut_slice(),
196                &UpgradeableLoaderState::ProgramData {
197                    slot: 0,
198                    upgrade_authority_address,
199                },
200            )
201            .map_err(|_| format!("Failed to write to upgradeable programdata account {address}"))
202        } else {
203            Err(format!(
204                "Failed to read upgradeable programdata account {address}"
205            ))
206        }
207    } else {
208        Err(format!("Account {address} not owned by upgradeable loader"))
209    }
210}
211
212impl TestValidatorGenesis {
213    /// Adds features to deactivate to a set, eliminating redundancies
214    /// during `initialize_ledger`, if member of the set is not a Feature
215    /// it will be silently ignored
216    pub fn deactivate_features(&mut self, deactivate_list: &[Pubkey]) -> &mut Self {
217        self.deactivate_feature_set.extend(deactivate_list);
218        self
219    }
220    pub fn ledger_path<P: Into<PathBuf>>(&mut self, ledger_path: P) -> &mut Self {
221        self.ledger_path = Some(ledger_path.into());
222        self
223    }
224
225    pub fn tower_storage(&mut self, tower_storage: Arc<dyn TowerStorage>) -> &mut Self {
226        self.tower_storage = Some(tower_storage);
227        self
228    }
229
230    /// Check if a given TestValidator ledger has already been initialized
231    pub fn ledger_exists(ledger_path: &Path) -> bool {
232        ledger_path.join("vote-account-keypair.json").exists()
233    }
234
235    pub fn tpu_enable_udp(&mut self, tpu_enable_udp: bool) -> &mut Self {
236        self.tpu_enable_udp = tpu_enable_udp;
237        self
238    }
239
240    pub fn fee_rate_governor(&mut self, fee_rate_governor: FeeRateGovernor) -> &mut Self {
241        self.fee_rate_governor = fee_rate_governor;
242        self
243    }
244
245    pub fn ticks_per_slot(&mut self, ticks_per_slot: u64) -> &mut Self {
246        self.ticks_per_slot = Some(ticks_per_slot);
247        self
248    }
249
250    pub fn epoch_schedule(&mut self, epoch_schedule: EpochSchedule) -> &mut Self {
251        self.epoch_schedule = Some(epoch_schedule);
252        self
253    }
254
255    pub fn rent(&mut self, rent: Rent) -> &mut Self {
256        self.rent = rent;
257        self
258    }
259
260    pub fn rpc_config(&mut self, rpc_config: JsonRpcConfig) -> &mut Self {
261        self.rpc_config = rpc_config;
262        self
263    }
264
265    pub fn pubsub_config(&mut self, pubsub_config: PubSubConfig) -> &mut Self {
266        self.pubsub_config = pubsub_config;
267        self
268    }
269
270    pub fn rpc_port(&mut self, rpc_port: u16) -> &mut Self {
271        self.rpc_ports = Some((rpc_port, rpc_port + 1));
272        self
273    }
274
275    pub fn faucet_addr(&mut self, faucet_addr: Option<SocketAddr>) -> &mut Self {
276        self.rpc_config.faucet_addr = faucet_addr;
277        self
278    }
279
280    pub fn warp_slot(&mut self, warp_slot: Slot) -> &mut Self {
281        self.warp_slot = Some(warp_slot);
282        self
283    }
284
285    pub fn gossip_host(&mut self, gossip_host: IpAddr) -> &mut Self {
286        self.node_config.gossip_addr.set_ip(gossip_host);
287        self
288    }
289
290    pub fn gossip_port(&mut self, gossip_port: u16) -> &mut Self {
291        self.node_config.gossip_addr.set_port(gossip_port);
292        self
293    }
294
295    pub fn port_range(&mut self, port_range: PortRange) -> &mut Self {
296        self.node_config.port_range = port_range;
297        self
298    }
299
300    pub fn bind_ip_addr(&mut self, bind_ip_addr: IpAddr) -> &mut Self {
301        self.node_config.bind_ip_addr = bind_ip_addr;
302        self
303    }
304
305    pub fn compute_unit_limit(&mut self, compute_unit_limit: u64) -> &mut Self {
306        self.compute_unit_limit = Some(compute_unit_limit);
307        self
308    }
309
310    /// Add an account to the test environment
311    pub fn add_account(&mut self, address: Pubkey, account: AccountSharedData) -> &mut Self {
312        self.accounts.insert(address, account);
313        self
314    }
315
316    pub fn add_accounts<T>(&mut self, accounts: T) -> &mut Self
317    where
318        T: IntoIterator<Item = (Pubkey, AccountSharedData)>,
319    {
320        for (address, account) in accounts {
321            self.add_account(address, account);
322        }
323        self
324    }
325
326    fn clone_accounts_and_transform<T, F>(
327        &mut self,
328        addresses: T,
329        rpc_client: &RpcClient,
330        skip_missing: bool,
331        transform: F,
332    ) -> Result<&mut Self, String>
333    where
334        T: IntoIterator<Item = Pubkey>,
335        F: Fn(&Pubkey, Account) -> Result<AccountSharedData, String>,
336    {
337        let addresses: Vec<Pubkey> = addresses.into_iter().collect();
338        for chunk in addresses.chunks(MAX_MULTIPLE_ACCOUNTS) {
339            info!("Fetching {:?} over RPC...", chunk);
340            let responses = rpc_client
341                .get_multiple_accounts(chunk)
342                .map_err(|err| format!("Failed to fetch: {err}"))?;
343            for (address, res) in chunk.iter().zip(responses) {
344                if let Some(account) = res {
345                    self.add_account(*address, transform(address, account)?);
346                } else if skip_missing {
347                    warn!("Could not find {}, skipping.", address);
348                } else {
349                    return Err(format!("Failed to fetch {address}"));
350                }
351            }
352        }
353        Ok(self)
354    }
355
356    pub fn clone_accounts<T>(
357        &mut self,
358        addresses: T,
359        rpc_client: &RpcClient,
360        skip_missing: bool,
361    ) -> Result<&mut Self, String>
362    where
363        T: IntoIterator<Item = Pubkey>,
364    {
365        self.clone_accounts_and_transform(
366            addresses,
367            rpc_client,
368            skip_missing,
369            |address, account| {
370                let mut account_shared_data = AccountSharedData::from(account);
371                // ignore the error
372                try_transform_program_data(address, &mut account_shared_data).ok();
373                Ok(account_shared_data)
374            },
375        )
376    }
377
378    pub fn clone_programdata_accounts<T>(
379        &mut self,
380        addresses: T,
381        rpc_client: &RpcClient,
382        skip_missing: bool,
383    ) -> Result<&mut Self, String>
384    where
385        T: IntoIterator<Item = Pubkey>,
386    {
387        self.clone_accounts_and_transform(
388            addresses,
389            rpc_client,
390            skip_missing,
391            |address, account| {
392                let mut account_shared_data = AccountSharedData::from(account);
393                try_transform_program_data(address, &mut account_shared_data)?;
394                Ok(account_shared_data)
395            },
396        )
397    }
398
399    pub fn clone_upgradeable_programs<T>(
400        &mut self,
401        addresses: T,
402        rpc_client: &RpcClient,
403    ) -> Result<&mut Self, String>
404    where
405        T: IntoIterator<Item = Pubkey>,
406    {
407        let addresses: Vec<Pubkey> = addresses.into_iter().collect();
408        self.clone_accounts(addresses.clone(), rpc_client, false)?;
409
410        let mut programdata_addresses: HashSet<Pubkey> = HashSet::new();
411        for address in addresses {
412            let account = self.accounts.get(&address).unwrap();
413
414            if let Ok(UpgradeableLoaderState::Program {
415                programdata_address,
416            }) = account.deserialize_data()
417            {
418                programdata_addresses.insert(programdata_address);
419            } else {
420                return Err(format!(
421                    "Failed to read upgradeable program account {address}",
422                ));
423            }
424        }
425
426        self.clone_programdata_accounts(programdata_addresses, rpc_client, false)?;
427
428        Ok(self)
429    }
430
431    pub fn clone_feature_set(&mut self, rpc_client: &RpcClient) -> Result<&mut Self, String> {
432        for feature_ids in FEATURE_NAMES
433            .keys()
434            .cloned()
435            .collect::<Vec<Pubkey>>()
436            .chunks(MAX_MULTIPLE_ACCOUNTS)
437        {
438            rpc_client
439                .get_multiple_accounts(feature_ids)
440                .map_err(|err| format!("Failed to fetch: {err}"))?
441                .into_iter()
442                .zip(feature_ids)
443                .for_each(|(maybe_account, feature_id)| {
444                    if maybe_account
445                        .as_ref()
446                        .and_then(solana_sdk::feature::from_account)
447                        .and_then(|feature| feature.activated_at)
448                        .is_none()
449                    {
450                        self.deactivate_feature_set.insert(*feature_id);
451                    }
452                });
453        }
454        Ok(self)
455    }
456
457    pub fn add_accounts_from_json_files(
458        &mut self,
459        accounts: &[AccountInfo],
460    ) -> Result<&mut Self, String> {
461        for account in accounts {
462            let Some(account_path) = solana_program_test::find_file(account.filename) else {
463                return Err(format!("Unable to locate {}", account.filename));
464            };
465            let mut file = File::open(&account_path).unwrap();
466            let mut account_info_raw = String::new();
467            file.read_to_string(&mut account_info_raw).unwrap();
468
469            let result: serde_json::Result<CliAccount> = serde_json::from_str(&account_info_raw);
470            let account_info = match result {
471                Err(err) => {
472                    return Err(format!(
473                        "Unable to deserialize {}: {}",
474                        account_path.to_str().unwrap(),
475                        err
476                    ));
477                }
478                Ok(deserialized) => deserialized,
479            };
480
481            let address = account.address.unwrap_or_else(|| {
482                Pubkey::from_str(account_info.keyed_account.pubkey.as_str()).unwrap()
483            });
484            let account = account_info
485                .keyed_account
486                .account
487                .decode::<AccountSharedData>()
488                .unwrap();
489
490            self.add_account(address, account);
491        }
492        Ok(self)
493    }
494
495    pub fn add_accounts_from_directories<T, P>(&mut self, dirs: T) -> Result<&mut Self, String>
496    where
497        T: IntoIterator<Item = P>,
498        P: AsRef<Path> + Display,
499    {
500        let mut json_files: HashSet<String> = HashSet::new();
501        for dir in dirs {
502            let matched_files = match fs::read_dir(&dir) {
503                Ok(dir) => dir,
504                Err(e) => return Err(format!("Cannot read directory {}: {}", &dir, e)),
505            }
506            .flatten()
507            .map(|entry| entry.path())
508            .filter(|path| path.is_file() && path.extension() == Some(OsStr::new("json")))
509            .map(|path| String::from(path.to_string_lossy()));
510
511            json_files.extend(matched_files);
512        }
513
514        debug!("account files found: {:?}", json_files);
515
516        let accounts: Vec<_> = json_files
517            .iter()
518            .map(|filename| AccountInfo {
519                address: None,
520                filename,
521            })
522            .collect();
523
524        self.add_accounts_from_json_files(&accounts)?;
525
526        Ok(self)
527    }
528
529    /// Add an account to the test environment with the account data in the provided `filename`
530    pub fn add_account_with_file_data(
531        &mut self,
532        address: Pubkey,
533        lamports: u64,
534        owner: Pubkey,
535        filename: &str,
536    ) -> &mut Self {
537        self.add_account(
538            address,
539            AccountSharedData::from(Account {
540                lamports,
541                data: solana_program_test::read_file(
542                    solana_program_test::find_file(filename).unwrap_or_else(|| {
543                        panic!("Unable to locate {filename}");
544                    }),
545                ),
546                owner,
547                executable: false,
548                rent_epoch: 0,
549            }),
550        )
551    }
552
553    /// Add an account to the test environment with the account data in the provided as a base 64
554    /// string
555    pub fn add_account_with_base64_data(
556        &mut self,
557        address: Pubkey,
558        lamports: u64,
559        owner: Pubkey,
560        data_base64: &str,
561    ) -> &mut Self {
562        self.add_account(
563            address,
564            AccountSharedData::from(Account {
565                lamports,
566                data: BASE64_STANDARD
567                    .decode(data_base64)
568                    .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
569                owner,
570                executable: false,
571                rent_epoch: 0,
572            }),
573        )
574    }
575
576    /// Add a SBF program to the test environment.
577    ///
578    /// `program_name` will also used to locate the SBF shared object in the current or fixtures
579    /// directory.
580    pub fn add_program(&mut self, program_name: &str, program_id: Pubkey) -> &mut Self {
581        let program_path = solana_program_test::find_file(&format!("{program_name}.so"))
582            .unwrap_or_else(|| panic!("Unable to locate program {program_name}"));
583
584        self.upgradeable_programs.push(UpgradeableProgramInfo {
585            program_id,
586            loader: solana_sdk::bpf_loader_upgradeable::id(),
587            upgrade_authority: Pubkey::default(),
588            program_path,
589        });
590        self
591    }
592
593    /// Add a list of upgradeable programs to the test environment.
594    pub fn add_upgradeable_programs_with_path(
595        &mut self,
596        programs: &[UpgradeableProgramInfo],
597    ) -> &mut Self {
598        for program in programs {
599            self.upgradeable_programs.push(program.clone());
600        }
601        self
602    }
603
604    /// Start a test validator with the address of the mint account that will receive tokens
605    /// created at genesis.
606    ///
607    pub fn start_with_mint_address(
608        &self,
609        mint_address: Pubkey,
610        socket_addr_space: SocketAddrSpace,
611    ) -> Result<TestValidator, Box<dyn std::error::Error>> {
612        self.start_with_mint_address_and_geyser_plugin_rpc(mint_address, socket_addr_space, None)
613    }
614
615    /// Start a test validator with the address of the mint account that will receive tokens
616    /// created at genesis. Augments admin rpc service with dynamic geyser plugin manager if
617    /// the geyser plugin service is enabled at startup.
618    ///
619    pub fn start_with_mint_address_and_geyser_plugin_rpc(
620        &self,
621        mint_address: Pubkey,
622        socket_addr_space: SocketAddrSpace,
623        rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
624    ) -> Result<TestValidator, Box<dyn std::error::Error>> {
625        TestValidator::start(
626            mint_address,
627            self,
628            socket_addr_space,
629            rpc_to_plugin_manager_receiver,
630        )
631        .inspect(|test_validator| {
632            let runtime = tokio::runtime::Builder::new_current_thread()
633                .enable_io()
634                .enable_time()
635                .build()
636                .unwrap();
637            runtime.block_on(test_validator.wait_for_nonzero_fees());
638        })
639    }
640
641    /// Start a test validator
642    ///
643    /// Returns a new `TestValidator` as well as the keypair for the mint account that will receive tokens
644    /// created at genesis.
645    ///
646    /// This function panics on initialization failure.
647    pub fn start(&self) -> (TestValidator, Keypair) {
648        self.start_with_socket_addr_space(SocketAddrSpace::new(/*allow_private_addr=*/ true))
649    }
650
651    /// Start a test validator with the given `SocketAddrSpace`
652    ///
653    /// Returns a new `TestValidator` as well as the keypair for the mint account that will receive tokens
654    /// created at genesis.
655    ///
656    /// This function panics on initialization failure.
657    pub fn start_with_socket_addr_space(
658        &self,
659        socket_addr_space: SocketAddrSpace,
660    ) -> (TestValidator, Keypair) {
661        let mint_keypair = Keypair::new();
662        self.start_with_mint_address(mint_keypair.pubkey(), socket_addr_space)
663            .map(|test_validator| (test_validator, mint_keypair))
664            .unwrap_or_else(|err| panic!("Test validator failed to start: {err}"))
665    }
666
667    pub async fn start_async(&self) -> (TestValidator, Keypair) {
668        self.start_async_with_socket_addr_space(SocketAddrSpace::new(
669            /*allow_private_addr=*/ true,
670        ))
671        .await
672    }
673
674    pub async fn start_async_with_socket_addr_space(
675        &self,
676        socket_addr_space: SocketAddrSpace,
677    ) -> (TestValidator, Keypair) {
678        let mint_keypair = Keypair::new();
679        match TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space, None) {
680            Ok(test_validator) => {
681                test_validator.wait_for_nonzero_fees().await;
682                (test_validator, mint_keypair)
683            }
684            Err(err) => panic!("Test validator failed to start: {err}"),
685        }
686    }
687}
688
689pub struct TestValidator {
690    ledger_path: PathBuf,
691    preserve_ledger: bool,
692    rpc_pubsub_url: String,
693    rpc_url: String,
694    tpu: SocketAddr,
695    gossip: SocketAddr,
696    validator: Option<Validator>,
697    vote_account_address: Pubkey,
698}
699
700impl TestValidator {
701    /// Create and start a `TestValidator` with no transaction fees and minimal rent.
702    /// Faucet optional.
703    ///
704    /// This function panics on initialization failure.
705    pub fn with_no_fees(
706        mint_address: Pubkey,
707        faucet_addr: Option<SocketAddr>,
708        socket_addr_space: SocketAddrSpace,
709    ) -> Self {
710        TestValidatorGenesis::default()
711            .fee_rate_governor(FeeRateGovernor::new(0, 0))
712            .rent(Rent {
713                lamports_per_byte_year: 1,
714                exemption_threshold: 1.0,
715                ..Rent::default()
716            })
717            .faucet_addr(faucet_addr)
718            .start_with_mint_address(mint_address, socket_addr_space)
719            .expect("validator start failed")
720    }
721
722    /// Create a test validator using udp for TPU.
723    pub fn with_no_fees_udp(
724        mint_address: Pubkey,
725        faucet_addr: Option<SocketAddr>,
726        socket_addr_space: SocketAddrSpace,
727    ) -> Self {
728        TestValidatorGenesis::default()
729            .tpu_enable_udp(true)
730            .fee_rate_governor(FeeRateGovernor::new(0, 0))
731            .rent(Rent {
732                lamports_per_byte_year: 1,
733                exemption_threshold: 1.0,
734                ..Rent::default()
735            })
736            .faucet_addr(faucet_addr)
737            .start_with_mint_address(mint_address, socket_addr_space)
738            .expect("validator start failed")
739    }
740
741    /// Create and start a `TestValidator` with custom transaction fees and minimal rent.
742    /// Faucet optional.
743    ///
744    /// This function panics on initialization failure.
745    pub fn with_custom_fees(
746        mint_address: Pubkey,
747        target_lamports_per_signature: u64,
748        faucet_addr: Option<SocketAddr>,
749        socket_addr_space: SocketAddrSpace,
750    ) -> Self {
751        TestValidatorGenesis::default()
752            .fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
753            .rent(Rent {
754                lamports_per_byte_year: 1,
755                exemption_threshold: 1.0,
756                ..Rent::default()
757            })
758            .faucet_addr(faucet_addr)
759            .start_with_mint_address(mint_address, socket_addr_space)
760            .expect("validator start failed")
761    }
762
763    /// allow tests to indicate that validator has completed initialization
764    pub fn set_startup_verification_complete_for_tests(&self) {
765        self.bank_forks()
766            .read()
767            .unwrap()
768            .root_bank()
769            .set_startup_verification_complete();
770    }
771
772    /// Initialize the ledger directory
773    ///
774    /// If `ledger_path` is `None`, a temporary ledger will be created.  Otherwise the ledger will
775    /// be initialized in the provided directory if it doesn't already exist.
776    ///
777    /// Returns the path to the ledger directory.
778    fn initialize_ledger(
779        mint_address: Pubkey,
780        config: &TestValidatorGenesis,
781    ) -> Result<PathBuf, Box<dyn std::error::Error>> {
782        let validator_identity = Keypair::new();
783        let validator_vote_account = Keypair::new();
784        let validator_stake_account = Keypair::new();
785        let validator_identity_lamports = sol_to_lamports(500.);
786        let validator_stake_lamports = sol_to_lamports(1_000_000.);
787        let mint_lamports = sol_to_lamports(500_000_000.);
788
789        let mut accounts = config.accounts.clone();
790        for (address, account) in solana_program_test::programs::spl_programs(&config.rent) {
791            accounts.entry(address).or_insert(account);
792        }
793        for upgradeable_program in &config.upgradeable_programs {
794            let data = solana_program_test::read_file(&upgradeable_program.program_path);
795            let (programdata_address, _) = Pubkey::find_program_address(
796                &[upgradeable_program.program_id.as_ref()],
797                &upgradeable_program.loader,
798            );
799            let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
800                slot: 0,
801                upgrade_authority_address: Some(upgradeable_program.upgrade_authority),
802            })
803            .unwrap();
804            program_data.extend_from_slice(&data);
805            accounts.insert(
806                programdata_address,
807                AccountSharedData::from(Account {
808                    lamports: Rent::default().minimum_balance(program_data.len()).max(1),
809                    data: program_data,
810                    owner: upgradeable_program.loader,
811                    executable: false,
812                    rent_epoch: 0,
813                }),
814            );
815
816            let data = bincode::serialize(&UpgradeableLoaderState::Program {
817                programdata_address,
818            })
819            .unwrap();
820            accounts.insert(
821                upgradeable_program.program_id,
822                AccountSharedData::from(Account {
823                    lamports: Rent::default().minimum_balance(data.len()).max(1),
824                    data,
825                    owner: upgradeable_program.loader,
826                    executable: true,
827                    rent_epoch: 0,
828                }),
829            );
830        }
831
832        let mut genesis_config = create_genesis_config_with_leader_ex(
833            mint_lamports,
834            &mint_address,
835            &validator_identity.pubkey(),
836            &validator_vote_account.pubkey(),
837            &validator_stake_account.pubkey(),
838            validator_stake_lamports,
839            validator_identity_lamports,
840            config.fee_rate_governor.clone(),
841            config.rent.clone(),
842            solana_sdk::genesis_config::ClusterType::Development,
843            accounts.into_iter().collect(),
844        );
845        genesis_config.epoch_schedule = config
846            .epoch_schedule
847            .as_ref()
848            .cloned()
849            .unwrap_or_else(EpochSchedule::without_warmup);
850
851        if let Some(ticks_per_slot) = config.ticks_per_slot {
852            genesis_config.ticks_per_slot = ticks_per_slot;
853        }
854
855        // Remove features tagged to deactivate
856        for deactivate_feature_pk in &config.deactivate_feature_set {
857            if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
858                match genesis_config.accounts.remove(deactivate_feature_pk) {
859                    Some(_) => info!("Feature for {:?} deactivated", deactivate_feature_pk),
860                    None => warn!(
861                        "Feature {:?} set for deactivation not found in genesis_config account list, ignored.",
862                        deactivate_feature_pk
863                    ),
864                }
865            } else {
866                warn!(
867                    "Feature {:?} set for deactivation is not a known Feature public key",
868                    deactivate_feature_pk
869                );
870            }
871        }
872
873        let ledger_path = match &config.ledger_path {
874            None => create_new_tmp_ledger!(&genesis_config).0,
875            Some(ledger_path) => {
876                if TestValidatorGenesis::ledger_exists(ledger_path) {
877                    return Ok(ledger_path.to_path_buf());
878                }
879
880                let _ = create_new_ledger(
881                    ledger_path,
882                    &genesis_config,
883                    config
884                        .max_genesis_archive_unpacked_size
885                        .unwrap_or(MAX_GENESIS_ARCHIVE_UNPACKED_SIZE),
886                    LedgerColumnOptions::default(),
887                )
888                .map_err(|err| {
889                    format!(
890                        "Failed to create ledger at {}: {}",
891                        ledger_path.display(),
892                        err
893                    )
894                })?;
895                ledger_path.to_path_buf()
896            }
897        };
898
899        write_keypair_file(
900            &validator_identity,
901            ledger_path.join("validator-keypair.json").to_str().unwrap(),
902        )?;
903
904        write_keypair_file(
905            &validator_stake_account,
906            ledger_path
907                .join("stake-account-keypair.json")
908                .to_str()
909                .unwrap(),
910        )?;
911
912        // `ledger_exists` should fail until the vote account keypair is written
913        assert!(!TestValidatorGenesis::ledger_exists(&ledger_path));
914
915        write_keypair_file(
916            &validator_vote_account,
917            ledger_path
918                .join("vote-account-keypair.json")
919                .to_str()
920                .unwrap(),
921        )?;
922
923        Ok(ledger_path)
924    }
925
926    /// Starts a TestValidator at the provided ledger directory
927    fn start(
928        mint_address: Pubkey,
929        config: &TestValidatorGenesis,
930        socket_addr_space: SocketAddrSpace,
931        rpc_to_plugin_manager_receiver: Option<Receiver<GeyserPluginManagerRequest>>,
932    ) -> Result<Self, Box<dyn std::error::Error>> {
933        let preserve_ledger = config.ledger_path.is_some();
934        let ledger_path = TestValidator::initialize_ledger(mint_address, config)?;
935
936        let validator_identity =
937            read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
938        let validator_vote_account = read_keypair_file(
939            ledger_path
940                .join("vote-account-keypair.json")
941                .to_str()
942                .unwrap(),
943        )?;
944
945        let mut node = Node::new_single_bind(
946            &validator_identity.pubkey(),
947            &config.node_config.gossip_addr,
948            config.node_config.port_range,
949            config.node_config.bind_ip_addr,
950        );
951        if let Some((rpc, rpc_pubsub)) = config.rpc_ports {
952            let addr = node.info.gossip().unwrap().ip();
953            node.info.set_rpc((addr, rpc)).unwrap();
954            node.info.set_rpc_pubsub((addr, rpc_pubsub)).unwrap();
955        }
956
957        let vote_account_address = validator_vote_account.pubkey();
958        let rpc_url = format!("http://{}", node.info.rpc().unwrap());
959        let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub().unwrap());
960        let tpu = node.info.tpu(Protocol::UDP).unwrap();
961        let gossip = node.info.gossip().unwrap();
962
963        {
964            let mut authorized_voter_keypairs = config.authorized_voter_keypairs.write().unwrap();
965            if !authorized_voter_keypairs
966                .iter()
967                .any(|x| x.pubkey() == vote_account_address)
968            {
969                authorized_voter_keypairs.push(Arc::new(validator_vote_account))
970            }
971        }
972
973        let accounts_db_config = Some(AccountsDbConfig {
974            index: Some(AccountsIndexConfig {
975                started_from_validator: true,
976                ..AccountsIndexConfig::default()
977            }),
978            account_indexes: Some(config.rpc_config.account_indexes.clone()),
979            ..AccountsDbConfig::default()
980        });
981
982        let runtime_config = RuntimeConfig {
983            compute_budget: config
984                .compute_unit_limit
985                .map(|compute_unit_limit| ComputeBudget {
986                    compute_unit_limit,
987                    ..ComputeBudget::default()
988                }),
989            log_messages_bytes_limit: config.log_messages_bytes_limit,
990            transaction_account_lock_limit: config.transaction_account_lock_limit,
991        };
992
993        let mut validator_config = ValidatorConfig {
994            on_start_geyser_plugin_config_files: config.geyser_plugin_config_files.clone(),
995            rpc_addrs: Some((
996                SocketAddr::new(
997                    IpAddr::V4(Ipv4Addr::UNSPECIFIED),
998                    node.info.rpc().unwrap().port(),
999                ),
1000                SocketAddr::new(
1001                    IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1002                    node.info.rpc_pubsub().unwrap().port(),
1003                ),
1004            )),
1005            rpc_config: config.rpc_config.clone(),
1006            pubsub_config: config.pubsub_config.clone(),
1007            accounts_hash_interval_slots: 100,
1008            account_paths: vec![
1009                create_accounts_run_and_snapshot_dirs(ledger_path.join("accounts"))
1010                    .unwrap()
1011                    .0,
1012            ],
1013            run_verification: false, // Skip PoH verification of ledger on startup for speed
1014            snapshot_config: SnapshotConfig {
1015                full_snapshot_archive_interval_slots: 100,
1016                incremental_snapshot_archive_interval_slots: Slot::MAX,
1017                bank_snapshots_dir: ledger_path.join("snapshot"),
1018                full_snapshot_archives_dir: ledger_path.to_path_buf(),
1019                incremental_snapshot_archives_dir: ledger_path.to_path_buf(),
1020                ..SnapshotConfig::default()
1021            },
1022            enforce_ulimit_nofile: false,
1023            warp_slot: config.warp_slot,
1024            validator_exit: config.validator_exit.clone(),
1025            max_ledger_shreds: config.max_ledger_shreds,
1026            no_wait_for_vote_to_start_leader: true,
1027            staked_nodes_overrides: config.staked_nodes_overrides.clone(),
1028            accounts_db_config,
1029            runtime_config,
1030            ..ValidatorConfig::default_for_test()
1031        };
1032        if let Some(ref tower_storage) = config.tower_storage {
1033            validator_config.tower_storage = tower_storage.clone();
1034        }
1035
1036        let validator = Some(Validator::new(
1037            node,
1038            Arc::new(validator_identity),
1039            &ledger_path,
1040            &vote_account_address,
1041            config.authorized_voter_keypairs.clone(),
1042            vec![],
1043            &validator_config,
1044            true, // should_check_duplicate_instance
1045            rpc_to_plugin_manager_receiver,
1046            config.start_progress.clone(),
1047            socket_addr_space,
1048            DEFAULT_TPU_USE_QUIC,
1049            DEFAULT_TPU_CONNECTION_POOL_SIZE,
1050            config.tpu_enable_udp,
1051            32, // max connections per IpAddr per minute for test
1052            config.admin_rpc_service_post_init.clone(),
1053        )?);
1054
1055        // Needed to avoid panics in `solana-responder-gossip` in tests that create a number of
1056        // test validators concurrently...
1057        discover_cluster(&gossip, 1, socket_addr_space)
1058            .map_err(|err| format!("TestValidator startup failed: {err:?}"))?;
1059
1060        let test_validator = TestValidator {
1061            ledger_path,
1062            preserve_ledger,
1063            rpc_pubsub_url,
1064            rpc_url,
1065            tpu,
1066            gossip,
1067            validator,
1068            vote_account_address,
1069        };
1070        Ok(test_validator)
1071    }
1072
1073    /// This is a hack to delay until the fees are non-zero for test consistency
1074    /// (fees from genesis are zero until the first block with a transaction in it is completed
1075    ///  due to a bug in the Bank)
1076    async fn wait_for_nonzero_fees(&self) {
1077        let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment(
1078            self.rpc_url.clone(),
1079            CommitmentConfig::processed(),
1080        );
1081        let mut message = Message::new(
1082            &[Instruction::new_with_bytes(
1083                Pubkey::new_unique(),
1084                &[],
1085                vec![AccountMeta::new(Pubkey::new_unique(), true)],
1086            )],
1087            None,
1088        );
1089        const MAX_TRIES: u64 = 10;
1090        let mut num_tries = 0;
1091        loop {
1092            num_tries += 1;
1093            if num_tries > MAX_TRIES {
1094                break;
1095            }
1096            println!("Waiting for fees to stabilize {num_tries:?}...");
1097            match rpc_client.get_latest_blockhash().await {
1098                Ok(blockhash) => {
1099                    message.recent_blockhash = blockhash;
1100                    match rpc_client.get_fee_for_message(&message).await {
1101                        Ok(fee) => {
1102                            if fee != 0 {
1103                                break;
1104                            }
1105                        }
1106                        Err(err) => {
1107                            warn!("get_fee_for_message() failed: {:?}", err);
1108                            break;
1109                        }
1110                    }
1111                }
1112                Err(err) => {
1113                    warn!("get_latest_blockhash() failed: {:?}", err);
1114                    break;
1115                }
1116            }
1117            sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await;
1118        }
1119    }
1120
1121    /// Return the validator's TPU address
1122    pub fn tpu(&self) -> &SocketAddr {
1123        &self.tpu
1124    }
1125
1126    /// Return the validator's Gossip address
1127    pub fn gossip(&self) -> &SocketAddr {
1128        &self.gossip
1129    }
1130
1131    /// Return the validator's JSON RPC URL
1132    pub fn rpc_url(&self) -> String {
1133        self.rpc_url.clone()
1134    }
1135
1136    /// Return the validator's JSON RPC PubSub URL
1137    pub fn rpc_pubsub_url(&self) -> String {
1138        self.rpc_pubsub_url.clone()
1139    }
1140
1141    /// Return the validator's vote account address
1142    pub fn vote_account_address(&self) -> Pubkey {
1143        self.vote_account_address
1144    }
1145
1146    /// Return an RpcClient for the validator.
1147    pub fn get_rpc_client(&self) -> RpcClient {
1148        RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed())
1149    }
1150
1151    /// Return a nonblocking RpcClient for the validator.
1152    pub fn get_async_rpc_client(&self) -> nonblocking::rpc_client::RpcClient {
1153        nonblocking::rpc_client::RpcClient::new_with_commitment(
1154            self.rpc_url.clone(),
1155            CommitmentConfig::processed(),
1156        )
1157    }
1158
1159    pub fn join(mut self) {
1160        if let Some(validator) = self.validator.take() {
1161            validator.join();
1162        }
1163    }
1164
1165    pub fn cluster_info(&self) -> Arc<ClusterInfo> {
1166        self.validator.as_ref().unwrap().cluster_info.clone()
1167    }
1168
1169    pub fn bank_forks(&self) -> Arc<RwLock<BankForks>> {
1170        self.validator.as_ref().unwrap().bank_forks.clone()
1171    }
1172
1173    pub fn repair_whitelist(&self) -> Arc<RwLock<HashSet<Pubkey>>> {
1174        Arc::new(RwLock::new(HashSet::default()))
1175    }
1176}
1177
1178impl Drop for TestValidator {
1179    fn drop(&mut self) {
1180        if let Some(validator) = self.validator.take() {
1181            validator.close();
1182        }
1183        if !self.preserve_ledger {
1184            remove_dir_all(&self.ledger_path).unwrap_or_else(|err| {
1185                panic!(
1186                    "Failed to remove ledger directory {}: {}",
1187                    self.ledger_path.display(),
1188                    err
1189                )
1190            });
1191        }
1192    }
1193}
1194
1195#[cfg(test)]
1196mod test {
1197    use super::*;
1198
1199    #[test]
1200    fn get_health() {
1201        let (test_validator, _payer) = TestValidatorGenesis::default().start();
1202        test_validator.set_startup_verification_complete_for_tests();
1203        let rpc_client = test_validator.get_rpc_client();
1204        rpc_client.get_health().expect("health");
1205    }
1206
1207    #[tokio::test]
1208    async fn nonblocking_get_health() {
1209        let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
1210        test_validator.set_startup_verification_complete_for_tests();
1211        let rpc_client = test_validator.get_async_rpc_client();
1212        rpc_client.get_health().await.expect("health");
1213    }
1214
1215    #[tokio::test]
1216    #[should_panic]
1217    async fn document_tokio_panic() {
1218        // `start()` blows up when run within tokio
1219        let (_test_validator, _payer) = TestValidatorGenesis::default().start();
1220    }
1221}