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