solana_test_validator/
lib.rs

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