Skip to main content

solana_svm/
program_loader.rs

1#[cfg(feature = "metrics")]
2use solana_program_runtime::program_metrics::LoadProgramMetrics;
3use {
4    solana_account::{AccountSharedData, ReadableAccount, state_traits::StateMut},
5    solana_clock::Slot,
6    solana_instruction::error::InstructionError,
7    solana_loader_v3_interface::state::UpgradeableLoaderState,
8    solana_loader_v4_interface::state::{LoaderV4State, LoaderV4Status},
9    solana_program_runtime::{
10        loaded_programs::{
11            ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironment,
12            ProgramToLoad,
13        },
14        program_cache_entry::{
15            DELAY_VISIBILITY_SLOT_OFFSET, ProgramCacheEntry, ProgramCacheEntryOwner,
16            ProgramCacheEntryType,
17        },
18    },
19    solana_pubkey::Pubkey,
20    solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4},
21    solana_svm_callback::TransactionProcessingCallback,
22    solana_svm_timings::ExecuteTimings,
23    solana_svm_type_overrides::sync::Arc,
24    solana_transaction_error::{TransactionError, TransactionResult},
25    std::sync::atomic::Ordering,
26};
27
28#[derive(Debug)]
29pub(crate) enum ProgramAccountLoadResult {
30    InvalidAccountData(ProgramCacheEntryOwner),
31    ProgramOfLoaderV1(AccountSharedData),
32    ProgramOfLoaderV2(AccountSharedData),
33    ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
34    ProgramOfLoaderV4(AccountSharedData, Slot),
35}
36
37pub(crate) fn load_program_accounts<CB: TransactionProcessingCallback>(
38    callbacks: &CB,
39    pubkey: &Pubkey,
40) -> Option<(ProgramAccountLoadResult, Slot)> {
41    let (program_account, last_modification_slot) = callbacks.get_account_shared_data(pubkey)?;
42
43    let load_result = if loader_v4::check_id(program_account.owner()) {
44        loader_v4_get_state(program_account.data())
45            .ok()
46            .and_then(|state| {
47                (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot)
48            })
49            .map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
50            .unwrap_or(ProgramAccountLoadResult::InvalidAccountData(
51                ProgramCacheEntryOwner::LoaderV4,
52            ))
53    } else if bpf_loader_upgradeable::check_id(program_account.owner()) {
54        if let Ok(UpgradeableLoaderState::Program {
55            programdata_address,
56        }) = program_account.state()
57        {
58            if let Some((programdata_account, _slot)) =
59                callbacks.get_account_shared_data(&programdata_address)
60            {
61                if bpf_loader_upgradeable::check_id(programdata_account.owner()) {
62                    if let Ok(UpgradeableLoaderState::ProgramData {
63                        slot,
64                        upgrade_authority_address: _,
65                    }) = programdata_account.state()
66                    {
67                        ProgramAccountLoadResult::ProgramOfLoaderV3(
68                            program_account,
69                            programdata_account,
70                            slot,
71                        )
72                    } else {
73                        ProgramAccountLoadResult::InvalidAccountData(
74                            ProgramCacheEntryOwner::LoaderV3,
75                        )
76                    }
77                } else {
78                    ProgramAccountLoadResult::InvalidAccountData(ProgramCacheEntryOwner::LoaderV3)
79                }
80            } else {
81                ProgramAccountLoadResult::InvalidAccountData(ProgramCacheEntryOwner::LoaderV3)
82            }
83        } else {
84            ProgramAccountLoadResult::InvalidAccountData(ProgramCacheEntryOwner::LoaderV3)
85        }
86    } else if bpf_loader::check_id(program_account.owner()) {
87        ProgramAccountLoadResult::ProgramOfLoaderV2(program_account)
88    } else if bpf_loader_deprecated::check_id(program_account.owner()) {
89        ProgramAccountLoadResult::ProgramOfLoaderV1(program_account)
90    } else {
91        return None;
92    };
93
94    Some((load_result, last_modification_slot))
95}
96
97/// Loads the program with the given pubkey.
98///
99/// If the account doesn't exist it returns `None`. If the account does exist, it must be a program
100/// account (belong to one of the program loaders). Returns `Some(InvalidAccountData)` if the program
101/// account is `Closed`, contains invalid data or any of the programdata accounts are invalid.
102pub fn load_program_with_pubkey<CB: TransactionProcessingCallback>(
103    callbacks: &CB,
104    program_runtime_environment: &ProgramRuntimeEnvironment,
105    pubkey: &Pubkey,
106    current_slot: Slot,
107    execute_timings: &mut ExecuteTimings,
108) -> Option<(Arc<ProgramCacheEntry>, Slot)> {
109    #[cfg(feature = "metrics")]
110    let mut load_program_metrics = LoadProgramMetrics {
111        program_id: pubkey.to_string(),
112        ..LoadProgramMetrics::default()
113    };
114    #[cfg(not(feature = "metrics"))]
115    let _ = execute_timings;
116
117    let (load_result, last_modification_slot) = load_program_accounts(callbacks, pubkey)?;
118    let loaded_program = match load_result {
119        ProgramAccountLoadResult::InvalidAccountData(owner) => Ok(
120            ProgramCacheEntry::new_tombstone(current_slot, owner, ProgramCacheEntryType::Closed),
121        ),
122
123        ProgramAccountLoadResult::ProgramOfLoaderV1(program_account) => ProgramCacheEntry::new(
124            program_account.owner(),
125            ProgramRuntimeEnvironment::clone(program_runtime_environment),
126            0,
127            DELAY_VISIBILITY_SLOT_OFFSET,
128            program_account.data(),
129            program_account.data().len(),
130            #[cfg(feature = "metrics")]
131            &mut load_program_metrics,
132        )
133        .map_err(|_| (0, ProgramCacheEntryOwner::LoaderV1)),
134
135        ProgramAccountLoadResult::ProgramOfLoaderV2(program_account) => ProgramCacheEntry::new(
136            program_account.owner(),
137            ProgramRuntimeEnvironment::clone(program_runtime_environment),
138            0,
139            DELAY_VISIBILITY_SLOT_OFFSET,
140            program_account.data(),
141            program_account.data().len(),
142            #[cfg(feature = "metrics")]
143            &mut load_program_metrics,
144        )
145        .map_err(|_| (0, ProgramCacheEntryOwner::LoaderV2)),
146
147        ProgramAccountLoadResult::ProgramOfLoaderV3(
148            program_account,
149            programdata_account,
150            deployment_slot,
151        ) => programdata_account
152            .data()
153            .get(UpgradeableLoaderState::size_of_programdata_metadata()..)
154            .ok_or(())
155            .and_then(|programdata| {
156                ProgramCacheEntry::new(
157                    program_account.owner(),
158                    ProgramRuntimeEnvironment::clone(program_runtime_environment),
159                    deployment_slot,
160                    deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
161                    programdata,
162                    program_account
163                        .data()
164                        .len()
165                        .saturating_add(programdata_account.data().len()),
166                    #[cfg(feature = "metrics")]
167                    &mut load_program_metrics,
168                )
169                .map_err(|_| ())
170            })
171            .map_err(|_| (deployment_slot, ProgramCacheEntryOwner::LoaderV3)),
172
173        ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, deployment_slot) => {
174            program_account
175                .data()
176                .get(LoaderV4State::program_data_offset()..)
177                .ok_or(())
178                .and_then(|elf_bytes| {
179                    ProgramCacheEntry::new(
180                        &loader_v4::id(),
181                        ProgramRuntimeEnvironment::clone(program_runtime_environment),
182                        deployment_slot,
183                        deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
184                        elf_bytes,
185                        program_account.data().len(),
186                        #[cfg(feature = "metrics")]
187                        &mut load_program_metrics,
188                    )
189                    .map_err(|_| ())
190                })
191                .map_err(|_| (deployment_slot, ProgramCacheEntryOwner::LoaderV4))
192        }
193    }
194    .unwrap_or_else(|(deployment_slot, owner)| {
195        let env = ProgramRuntimeEnvironment::clone(program_runtime_environment);
196        ProgramCacheEntry::new_tombstone(
197            deployment_slot,
198            owner,
199            ProgramCacheEntryType::FailedVerification(env),
200        )
201    });
202
203    #[cfg(feature = "metrics")]
204    load_program_metrics.submit_datapoint(&mut execute_timings.details);
205    loaded_program.update_access_slot(current_slot);
206    Some((Arc::new(loaded_program), last_modification_slot))
207}
208
209/// Find the slot in which the program was most recently re-/deployed.
210/// Returns slot 0 for programs deployed with v1/v2 loaders, since programs deployed
211/// with those loaders do not retain deployment slot information.
212/// Returns an error if the program's account state can not be found or parsed.
213pub(crate) fn get_program_deployment_slot<CB: TransactionProcessingCallback>(
214    callbacks: &CB,
215    program: &AccountSharedData,
216    loader: ProgramCacheEntryOwner,
217) -> TransactionResult<Slot> {
218    match loader {
219        ProgramCacheEntryOwner::LoaderV1 | ProgramCacheEntryOwner::LoaderV2 => Ok(0),
220        ProgramCacheEntryOwner::LoaderV3 => {
221            if let Ok(UpgradeableLoaderState::Program {
222                programdata_address,
223            }) = program.state()
224            {
225                let (programdata, _slot) = callbacks
226                    .get_account_shared_data(&programdata_address)
227                    .ok_or(TransactionError::ProgramAccountNotFound)?;
228                if let Ok(UpgradeableLoaderState::ProgramData {
229                    slot,
230                    upgrade_authority_address: _,
231                }) = programdata.state()
232                {
233                    return Ok(slot);
234                }
235            }
236            Err(TransactionError::ProgramAccountNotFound)
237        }
238        ProgramCacheEntryOwner::LoaderV4 => {
239            let state = loader_v4_get_state(program.data())
240                .map_err(|_| TransactionError::ProgramAccountNotFound)?;
241            Ok(state.slot)
242        }
243        ProgramCacheEntryOwner::NativeLoader => unreachable!(),
244    }
245}
246
247/// Appends to a set of executable program accounts (all accounts owned by any loader)
248/// for transactions with a valid blockhash or nonce.
249pub fn filter_executable_program_accounts<'a, CB: TransactionProcessingCallback>(
250    callbacks: &CB,
251    program_cache_for_tx_batch: &ProgramCacheForTxBatch,
252    keys: impl Iterator<Item = &'a Pubkey>,
253    check_program_deployment_slot: bool,
254) -> Vec<ProgramToLoad<'a>> {
255    let mut result = Vec::new();
256    for account_key in keys {
257        if let Some(cache_entry) = program_cache_for_tx_batch.find(account_key) {
258            cache_entry.stats.uses.fetch_add(1, Ordering::Relaxed);
259        } else if let Some((account, last_modification_slot)) =
260            callbacks.get_account_shared_data(account_key)
261        {
262            let loader = if loader_v4::check_id(account.owner()) {
263                ProgramCacheEntryOwner::LoaderV4
264            } else if bpf_loader_upgradeable::check_id(account.owner()) {
265                ProgramCacheEntryOwner::LoaderV3
266            } else if bpf_loader::check_id(account.owner()) {
267                ProgramCacheEntryOwner::LoaderV2
268            } else if bpf_loader_deprecated::check_id(account.owner()) {
269                ProgramCacheEntryOwner::LoaderV1
270            } else {
271                continue;
272            };
273            let match_criteria = if check_program_deployment_slot {
274                get_program_deployment_slot(callbacks, &account, loader)
275                    .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| {
276                        ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot)
277                    })
278            } else {
279                ProgramCacheMatchCriteria::NoCriteria
280            };
281            result.push(ProgramToLoad {
282                program_id: account_key,
283                loader,
284                match_criteria,
285                last_modification_slot,
286            });
287        }
288    }
289    result
290}
291
292// Plucked from the now-removed Loader V4 program library.
293fn loader_v4_get_state(data: &[u8]) -> Result<&LoaderV4State, InstructionError> {
294    unsafe {
295        let data = data
296            .get(0..LoaderV4State::program_data_offset())
297            .ok_or(InstructionError::AccountDataTooSmall)?
298            .try_into()
299            .unwrap();
300        Ok(std::mem::transmute::<
301            &[u8; LoaderV4State::program_data_offset()],
302            &LoaderV4State,
303        >(data))
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use {
310        super::*,
311        crate::transaction_processor::TransactionBatchProcessor,
312        solana_account::WritableAccount,
313        solana_hash::Hash,
314        solana_keypair::Keypair,
315        solana_message::compiled_instruction::CompiledInstruction,
316        solana_program_runtime::{
317            loaded_programs::{
318                BlockRelation, ForkGraph, ProgramRuntimeEnvironment,
319                get_mock_program_runtime_environment,
320            },
321            solana_sbpf::program::BuiltinProgram,
322        },
323        solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, native_loader},
324        solana_svm_transaction::svm_message::SVMMessage,
325        solana_svm_type_overrides::sync::atomic::AtomicU64,
326        solana_transaction::{Transaction, sanitized::SanitizedTransaction},
327        std::{
328            cell::RefCell,
329            collections::HashMap,
330            env,
331            fs::{self, File},
332            io::Read,
333        },
334    };
335
336    struct TestForkGraph {}
337
338    impl ForkGraph for TestForkGraph {
339        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
340            BlockRelation::Unknown
341        }
342    }
343
344    #[derive(Default, Clone)]
345    pub(crate) struct MockBankCallback {
346        pub(crate) account_shared_data: RefCell<HashMap<Pubkey, (AccountSharedData, Slot)>>,
347    }
348
349    impl TransactionProcessingCallback for MockBankCallback {
350        fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> {
351            self.account_shared_data.borrow().get(pubkey).cloned()
352        }
353    }
354
355    #[test]
356    fn test_load_program_accounts_account_not_found() {
357        let mock_bank = MockBankCallback::default();
358        let key = Pubkey::new_unique();
359
360        let result = load_program_accounts(&mock_bank, &key);
361        assert!(result.is_none());
362
363        let mut account_data = AccountSharedData::default();
364        account_data.set_owner(bpf_loader_upgradeable::id());
365        let state = UpgradeableLoaderState::Program {
366            programdata_address: Pubkey::new_unique(),
367        };
368        account_data.set_data(bincode::serialize(&state).unwrap());
369        mock_bank
370            .account_shared_data
371            .borrow_mut()
372            .insert(key, (account_data.clone(), 0));
373
374        let result = load_program_accounts(&mock_bank, &key);
375        assert!(matches!(
376            result,
377            Some((ProgramAccountLoadResult::InvalidAccountData(_), _))
378        ));
379
380        account_data.set_data(Vec::new());
381        mock_bank
382            .account_shared_data
383            .borrow_mut()
384            .insert(key, (account_data, 0));
385
386        let result = load_program_accounts(&mock_bank, &key);
387
388        assert!(matches!(
389            result,
390            Some((ProgramAccountLoadResult::InvalidAccountData(_), _))
391        ));
392    }
393
394    #[test]
395    fn test_load_program_accounts_loader_v1_or_v2() {
396        let key = Pubkey::new_unique();
397        let mock_bank = MockBankCallback::default();
398        let mut account_data = AccountSharedData::default();
399        account_data.set_owner(bpf_loader::id());
400        mock_bank
401            .account_shared_data
402            .borrow_mut()
403            .insert(key, (account_data.clone(), 0));
404
405        let result = load_program_accounts(&mock_bank, &key);
406        match result {
407            Some((ProgramAccountLoadResult::ProgramOfLoaderV1(data), last_modification_slot))
408            | Some((ProgramAccountLoadResult::ProgramOfLoaderV2(data), last_modification_slot)) => {
409                assert_eq!(data, account_data);
410                assert_eq!(last_modification_slot, 0);
411            }
412            _ => panic!("Invalid result"),
413        }
414    }
415
416    #[test]
417    fn test_load_program_accounts_success() {
418        let key1 = Pubkey::new_unique();
419        let key2 = Pubkey::new_unique();
420        let mock_bank = MockBankCallback::default();
421
422        let mut account_data = AccountSharedData::default();
423        account_data.set_owner(bpf_loader_upgradeable::id());
424
425        let state = UpgradeableLoaderState::Program {
426            programdata_address: key2,
427        };
428        account_data.set_data(bincode::serialize(&state).unwrap());
429        mock_bank
430            .account_shared_data
431            .borrow_mut()
432            .insert(key1, (account_data.clone(), 25));
433
434        let state = UpgradeableLoaderState::ProgramData {
435            slot: 25,
436            upgrade_authority_address: None,
437        };
438        let mut account_data2 = AccountSharedData::default();
439        account_data2.set_owner(bpf_loader_upgradeable::id());
440        account_data2.set_data(bincode::serialize(&state).unwrap());
441        mock_bank
442            .account_shared_data
443            .borrow_mut()
444            .insert(key2, (account_data2.clone(), 25));
445
446        let result = load_program_accounts(&mock_bank, &key1);
447
448        match result {
449            Some((
450                ProgramAccountLoadResult::ProgramOfLoaderV3(data1, data2, deployment_slot),
451                last_modification_slot,
452            )) => {
453                assert_eq!(data1, account_data);
454                assert_eq!(data2, account_data2);
455                assert_eq!(deployment_slot, 25);
456                assert_eq!(last_modification_slot, 25);
457            }
458
459            _ => panic!("Invalid result"),
460        }
461    }
462
463    fn load_test_program() -> Vec<u8> {
464        let mut dir = env::current_dir().unwrap();
465        dir.push("tests");
466        dir.push("example-programs");
467        dir.push("hello-solana");
468        dir.push("hello_solana_program.so");
469        let mut file = File::open(dir.clone()).expect("file not found");
470        let metadata = fs::metadata(dir).expect("Unable to read metadata");
471        let mut buffer = vec![0; metadata.len() as usize];
472        file.read_exact(&mut buffer).expect("Buffer overflow");
473        buffer
474    }
475
476    #[test]
477    fn test_load_program_from_bytes() {
478        let buffer = load_test_program();
479
480        #[cfg(feature = "metrics")]
481        let mut metrics = LoadProgramMetrics::default();
482        let loader = bpf_loader_upgradeable::id();
483        let size = buffer.len();
484        let slot: Slot = 2;
485        let environment = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
486
487        let result = ProgramCacheEntry::new(
488            &loader,
489            ProgramRuntimeEnvironment::clone(&environment),
490            slot,
491            slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
492            &buffer,
493            size,
494            #[cfg(feature = "metrics")]
495            &mut metrics,
496        );
497
498        assert!(result.is_ok());
499    }
500
501    #[test]
502    fn test_load_program_not_found() {
503        let mock_bank = MockBankCallback::default();
504        let key = Pubkey::new_unique();
505        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
506
507        let result = load_program_with_pubkey(
508            &mock_bank,
509            &batch_processor.program_runtime_environment_for_epoch(50),
510            &key,
511            500,
512            &mut ExecuteTimings::default(),
513        );
514        assert!(result.is_none());
515    }
516
517    #[test]
518    fn test_load_program_invalid_account_data() {
519        let key = Pubkey::new_unique();
520        let mock_bank = MockBankCallback::default();
521        let mut account_data = AccountSharedData::default();
522        account_data.set_owner(bpf_loader_upgradeable::id());
523        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
524        mock_bank
525            .account_shared_data
526            .borrow_mut()
527            .insert(key, (account_data.clone(), 0));
528
529        let result = load_program_with_pubkey(
530            &mock_bank,
531            &batch_processor.program_runtime_environment_for_epoch(20),
532            &key,
533            0, // Slot 0
534            &mut ExecuteTimings::default(),
535        );
536
537        let loaded_program = ProgramCacheEntry::new_tombstone(
538            0, // Slot 0
539            ProgramCacheEntryOwner::LoaderV3,
540            ProgramCacheEntryType::FailedVerification(
541                batch_processor.program_runtime_environment_for_epoch(20),
542            ),
543        );
544        assert_eq!(result.unwrap(), (Arc::new(loaded_program), 0));
545    }
546
547    #[test]
548    fn test_load_program_program_loader_v1_or_v2() {
549        let key = Pubkey::new_unique();
550        let mock_bank = MockBankCallback::default();
551        let mut account_data = AccountSharedData::default();
552        account_data.set_owner(bpf_loader::id());
553        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
554        mock_bank
555            .account_shared_data
556            .borrow_mut()
557            .insert(key, (account_data.clone(), 0));
558
559        // This should return an error
560        let result = load_program_with_pubkey(
561            &mock_bank,
562            &batch_processor.program_runtime_environment_for_epoch(20),
563            &key,
564            200,
565            &mut ExecuteTimings::default(),
566        );
567        let loaded_program = ProgramCacheEntry::new_tombstone(
568            0,
569            ProgramCacheEntryOwner::LoaderV2,
570            ProgramCacheEntryType::FailedVerification(
571                batch_processor.program_runtime_environment_for_epoch(20),
572            ),
573        );
574        assert_eq!(result.unwrap(), (Arc::new(loaded_program), 0));
575
576        let buffer = load_test_program();
577        account_data.set_data(buffer);
578
579        mock_bank
580            .account_shared_data
581            .borrow_mut()
582            .insert(key, (account_data.clone(), 0));
583
584        let result = load_program_with_pubkey(
585            &mock_bank,
586            &batch_processor.program_runtime_environment_for_epoch(20),
587            &key,
588            200,
589            &mut ExecuteTimings::default(),
590        );
591
592        let program_runtime_environment = get_mock_program_runtime_environment();
593        let expected = ProgramCacheEntry::new(
594            account_data.owner(),
595            ProgramRuntimeEnvironment::clone(&program_runtime_environment),
596            0,
597            DELAY_VISIBILITY_SLOT_OFFSET,
598            account_data.data(),
599            account_data.data().len(),
600            #[cfg(feature = "metrics")]
601            &mut LoadProgramMetrics::default(),
602        );
603
604        assert_eq!(result.unwrap(), (Arc::new(expected.unwrap()), 0));
605    }
606
607    #[test]
608    fn test_load_program_program_loader_v3() {
609        let key1 = Pubkey::new_unique();
610        let key2 = Pubkey::new_unique();
611        let mock_bank = MockBankCallback::default();
612        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
613
614        let mut account_data = AccountSharedData::default();
615        account_data.set_owner(bpf_loader_upgradeable::id());
616
617        let state = UpgradeableLoaderState::Program {
618            programdata_address: key2,
619        };
620        account_data.set_data(bincode::serialize(&state).unwrap());
621        mock_bank
622            .account_shared_data
623            .borrow_mut()
624            .insert(key1, (account_data.clone(), 0));
625
626        let state = UpgradeableLoaderState::ProgramData {
627            slot: 0,
628            upgrade_authority_address: None,
629        };
630        let mut account_data2 = AccountSharedData::default();
631        account_data2.set_data(bincode::serialize(&state).unwrap());
632        mock_bank
633            .account_shared_data
634            .borrow_mut()
635            .insert(key2, (account_data2.clone(), 0));
636
637        // This should return an error
638        let result = load_program_with_pubkey(
639            &mock_bank,
640            &batch_processor.program_runtime_environment_for_epoch(0),
641            &key1,
642            0,
643            &mut ExecuteTimings::default(),
644        );
645        let loaded_program = ProgramCacheEntry::new_tombstone(
646            0,
647            ProgramCacheEntryOwner::LoaderV3,
648            ProgramCacheEntryType::FailedVerification(
649                batch_processor.program_runtime_environment_for_epoch(0),
650            ),
651        );
652        assert_eq!(result.unwrap(), (Arc::new(loaded_program), 0));
653
654        let mut buffer = load_test_program();
655        let mut header = bincode::serialize(&state).unwrap();
656        let mut complement = vec![
657            0;
658            std::cmp::max(
659                0,
660                UpgradeableLoaderState::size_of_programdata_metadata() - header.len()
661            )
662        ];
663        header.append(&mut complement);
664        header.append(&mut buffer);
665        account_data.set_data(header);
666
667        mock_bank
668            .account_shared_data
669            .borrow_mut()
670            .insert(key2, (account_data.clone(), 0));
671
672        let result = load_program_with_pubkey(
673            &mock_bank,
674            &batch_processor.program_runtime_environment_for_epoch(20),
675            &key1,
676            200,
677            &mut ExecuteTimings::default(),
678        );
679
680        let data = account_data.data();
681        account_data
682            .set_data(data[UpgradeableLoaderState::size_of_programdata_metadata()..].to_vec());
683
684        let program_runtime_environment = get_mock_program_runtime_environment();
685        let expected = ProgramCacheEntry::new(
686            account_data.owner(),
687            ProgramRuntimeEnvironment::clone(&program_runtime_environment),
688            0,
689            DELAY_VISIBILITY_SLOT_OFFSET,
690            account_data.data(),
691            account_data.data().len(),
692            #[cfg(feature = "metrics")]
693            &mut LoadProgramMetrics::default(),
694        );
695        assert_eq!(result.unwrap(), (Arc::new(expected.unwrap()), 0));
696    }
697
698    #[test]
699    fn test_load_program_environment() {
700        let key = Pubkey::new_unique();
701        let mock_bank = MockBankCallback::default();
702        let mut account_data = AccountSharedData::default();
703        account_data.set_owner(bpf_loader::id());
704        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
705        let upcoming_environment = get_mock_program_runtime_environment();
706        let current_environment =
707            ProgramRuntimeEnvironment::clone(&batch_processor.program_runtime_environment);
708        {
709            let mut epoch_boundary_preparation =
710                batch_processor.epoch_boundary_preparation.write().unwrap();
711            epoch_boundary_preparation.upcoming_epoch = 1;
712            epoch_boundary_preparation.upcoming_environment = Some(upcoming_environment.clone());
713        }
714        mock_bank
715            .account_shared_data
716            .borrow_mut()
717            .insert(key, (account_data.clone(), 0));
718
719        for is_upcoming_env in [false, true] {
720            let (result, _last_modification_slot) = load_program_with_pubkey(
721                &mock_bank,
722                &batch_processor.program_runtime_environment_for_epoch(is_upcoming_env as u64),
723                &key,
724                200,
725                &mut ExecuteTimings::default(),
726            )
727            .unwrap();
728            assert_ne!(
729                is_upcoming_env,
730                result.program.get_environment().unwrap() == &current_environment,
731            );
732            assert_eq!(
733                is_upcoming_env,
734                result.program.get_environment().unwrap() == &upcoming_environment,
735            );
736        }
737    }
738
739    #[test]
740    fn test_program_modification_slot_account_not_found() {
741        let mock_bank = MockBankCallback::default();
742        let key = Pubkey::new_unique();
743
744        let mut account_data = AccountSharedData::new(100, 100, &bpf_loader_upgradeable::id());
745        mock_bank
746            .account_shared_data
747            .borrow_mut()
748            .insert(key, (account_data.clone(), 0));
749
750        let result = get_program_deployment_slot(
751            &mock_bank,
752            &mock_bank.get_account_shared_data(&key).unwrap().0,
753            ProgramCacheEntryOwner::LoaderV3,
754        );
755        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
756
757        let state = UpgradeableLoaderState::Program {
758            programdata_address: Pubkey::new_unique(),
759        };
760        account_data.set_data(bincode::serialize(&state).unwrap());
761        mock_bank
762            .account_shared_data
763            .borrow_mut()
764            .insert(key, (account_data.clone(), 0));
765
766        let result = get_program_deployment_slot(
767            &mock_bank,
768            &mock_bank.get_account_shared_data(&key).unwrap().0,
769            ProgramCacheEntryOwner::LoaderV3,
770        );
771        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
772    }
773
774    #[test]
775    fn test_program_deployment_slot_success() {
776        let mock_bank = MockBankCallback::default();
777
778        let key1 = Pubkey::new_unique();
779        let key2 = Pubkey::new_unique();
780
781        let account_data = AccountSharedData::new_data(
782            100,
783            &UpgradeableLoaderState::Program {
784                programdata_address: key2,
785            },
786            &bpf_loader_upgradeable::id(),
787        )
788        .unwrap();
789        mock_bank
790            .account_shared_data
791            .borrow_mut()
792            .insert(key1, (account_data, 0));
793
794        let account_data = AccountSharedData::new_data(
795            100,
796            &UpgradeableLoaderState::ProgramData {
797                slot: 77,
798                upgrade_authority_address: None,
799            },
800            &bpf_loader_upgradeable::id(),
801        )
802        .unwrap();
803        mock_bank
804            .account_shared_data
805            .borrow_mut()
806            .insert(key2, (account_data.clone(), 0));
807
808        let result = get_program_deployment_slot(
809            &mock_bank,
810            &mock_bank.get_account_shared_data(&key1).unwrap().0,
811            ProgramCacheEntryOwner::LoaderV3,
812        );
813        assert_eq!(result.unwrap(), 77);
814    }
815
816    #[test]
817    fn test_filter_executable_program_accounts() {
818        let feepayer = Keypair::new();
819        let loader_ids = [
820            bpf_loader_deprecated::id(),
821            bpf_loader::id(),
822            bpf_loader_upgradeable::id(),
823            native_loader::id(),
824        ];
825        let program_ids = [
826            Pubkey::new_unique(),
827            Pubkey::new_unique(),
828            Pubkey::new_unique(),
829            Pubkey::new_unique(),
830        ];
831        let account_ids = [
832            Pubkey::new_unique(),
833            Pubkey::new_unique(),
834            Pubkey::new_unique(),
835            Pubkey::new_unique(),
836        ];
837
838        let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::default();
839        let mock_bank = MockBankCallback::default();
840        for i in 0..3 {
841            loaded_programs_for_tx_batch.replenish(
842                loader_ids[i],
843                Arc::new(ProgramCacheEntry {
844                    program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
845                    account_owner: ProgramCacheEntryOwner::NativeLoader,
846                    account_size: 0,
847                    deployment_slot: 0,
848                    effective_slot: 0,
849                    stats: Arc::default(),
850                    latest_access_slot: AtomicU64::default(),
851                }),
852            );
853            mock_bank.account_shared_data.borrow_mut().insert(
854                loader_ids[i],
855                (AccountSharedData::new(1, 1, &program_ids[3]), 0),
856            );
857            mock_bank.account_shared_data.borrow_mut().insert(
858                program_ids[i],
859                (AccountSharedData::new(1, 1, &loader_ids[i]), 0),
860            );
861            mock_bank.account_shared_data.borrow_mut().insert(
862                account_ids[i],
863                (AccountSharedData::new(1, 1, &program_ids[i]), 0),
864            );
865        }
866
867        let tx = Transaction::new_with_compiled_instructions(
868            &[&feepayer],
869            &[program_ids[1], program_ids[2], loader_ids[2]],
870            Hash::new_unique(),
871            vec![
872                account_ids[0],
873                account_ids[1],
874                account_ids[2],
875                account_ids[3],
876            ],
877            vec![
878                CompiledInstruction::new(1, &(), vec![0, 1, 2, 3]),
879                CompiledInstruction::new(2, &(), vec![0, 1, 2, 3]),
880                CompiledInstruction::new(3, &(), vec![0, 1, 2, 3]),
881            ],
882        );
883        let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx);
884
885        let missing_programs = filter_executable_program_accounts(
886            &mock_bank,
887            &loaded_programs_for_tx_batch,
888            sanitized_tx.account_keys().iter(),
889            false,
890        );
891        assert_eq!(
892            missing_programs,
893            &[
894                ProgramToLoad {
895                    program_id: &program_ids[1],
896                    loader: ProgramCacheEntryOwner::LoaderV2,
897                    match_criteria: ProgramCacheMatchCriteria::NoCriteria,
898                    last_modification_slot: 0,
899                },
900                ProgramToLoad {
901                    program_id: &program_ids[2],
902                    loader: ProgramCacheEntryOwner::LoaderV3,
903                    match_criteria: ProgramCacheMatchCriteria::NoCriteria,
904                    last_modification_slot: 0,
905                },
906            ]
907        );
908
909        let missing_programs = filter_executable_program_accounts(
910            &mock_bank,
911            &loaded_programs_for_tx_batch,
912            sanitized_tx.account_keys().iter(),
913            true,
914        );
915        assert_eq!(
916            missing_programs,
917            &[
918                ProgramToLoad {
919                    program_id: &program_ids[1],
920                    loader: ProgramCacheEntryOwner::LoaderV2,
921                    match_criteria: ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0),
922                    last_modification_slot: 0,
923                },
924                ProgramToLoad {
925                    program_id: &program_ids[2],
926                    loader: ProgramCacheEntryOwner::LoaderV3,
927                    match_criteria: ProgramCacheMatchCriteria::Tombstone,
928                    last_modification_slot: 0,
929                },
930            ]
931        );
932    }
933}