diff --git a/svm/src/lib.rs b/svm/src/lib.rs index 22893ecbd2f5ea..a79648d5e333a4 100644 --- a/svm/src/lib.rs +++ b/svm/src/lib.rs @@ -5,6 +5,7 @@ pub mod account_loader; pub mod account_overrides; pub mod account_rent_state; pub mod message_processor; +pub mod program_loader; pub mod transaction_account_state_info; pub mod transaction_error_metrics; pub mod transaction_processing_callback; diff --git a/svm/src/program_loader.rs b/svm/src/program_loader.rs new file mode 100644 index 00000000000000..7d5ad4970118f5 --- /dev/null +++ b/svm/src/program_loader.rs @@ -0,0 +1,383 @@ +use { + crate::transaction_processing_callback::TransactionProcessingCallback, + solana_program_runtime::loaded_programs::{ + LoadProgramMetrics, LoadedProgram, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, + DELAY_VISIBILITY_SLOT_OFFSET, + }, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount}, + account_utils::StateMut, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + clock::Slot, + loader_v4::{self, LoaderV4Status}, + pubkey::Pubkey, + }, +}; + +#[derive(Debug)] +pub(crate) enum ProgramAccountLoadResult { + AccountNotFound, + InvalidAccountData(ProgramRuntimeEnvironment), + ProgramOfLoaderV1orV2(AccountSharedData), + ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot), + ProgramOfLoaderV4(AccountSharedData, Slot), +} + +pub(crate) fn load_program_from_bytes( + load_program_metrics: &mut LoadProgramMetrics, + programdata: &[u8], + loader_key: &Pubkey, + account_size: usize, + deployment_slot: Slot, + program_runtime_environment: ProgramRuntimeEnvironment, + reloading: bool, +) -> std::result::Result> { + if reloading { + // Safety: this is safe because the program is being reloaded in the cache. + unsafe { + LoadedProgram::reload( + loader_key, + program_runtime_environment.clone(), + deployment_slot, + deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), + programdata, + account_size, + load_program_metrics, + ) + } + } else { + LoadedProgram::new( + loader_key, + program_runtime_environment.clone(), + deployment_slot, + deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), + programdata, + account_size, + load_program_metrics, + ) + } +} + +pub(crate) fn load_program_accounts( + callbacks: &CB, + pubkey: &Pubkey, + environments: &ProgramRuntimeEnvironments, +) -> ProgramAccountLoadResult { + let program_account = match callbacks.get_account_shared_data(pubkey) { + None => return ProgramAccountLoadResult::AccountNotFound, + Some(account) => account, + }; + + debug_assert!(solana_bpf_loader_program::check_loader_id( + program_account.owner() + )); + + if loader_v4::check_id(program_account.owner()) { + return solana_loader_v4_program::get_state(program_account.data()) + .ok() + .and_then(|state| { + (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot) + }) + .map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot)) + .unwrap_or(ProgramAccountLoadResult::InvalidAccountData( + environments.program_runtime_v2.clone(), + )); + } + + if !bpf_loader_upgradeable::check_id(program_account.owner()) { + return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account); + } + + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = program_account.state() + { + let programdata_account = match callbacks.get_account_shared_data(&programdata_address) { + None => return ProgramAccountLoadResult::AccountNotFound, + Some(account) => account, + }; + + if let Ok(UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: _, + }) = programdata_account.state() + { + return ProgramAccountLoadResult::ProgramOfLoaderV3( + program_account, + programdata_account, + slot, + ); + } + } + ProgramAccountLoadResult::InvalidAccountData(environments.program_runtime_v1.clone()) +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_program_runtime::{ + loaded_programs::{ + BlockRelation, ForkGraph, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, + }, + solana_rbpf::program::BuiltinProgram, + }, + solana_sdk::{ + account::WritableAccount, + bpf_loader, + feature_set::FeatureSet, + hash::Hash, + loader_v4::{self, LoaderV4State, LoaderV4Status}, + rent_collector::RentCollector, + }, + std::{ + collections::HashMap, + env, + fs::{self, File}, + io::Read, + sync::Arc, + }, + }; + + struct TestForkGraph {} + + impl ForkGraph for TestForkGraph { + fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation { + BlockRelation::Unknown + } + } + + #[derive(Default, Clone)] + pub struct MockBankCallback { + rent_collector: RentCollector, + feature_set: Arc, + pub account_shared_data: HashMap, + } + + impl TransactionProcessingCallback for MockBankCallback { + fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option { + if let Some(data) = self.account_shared_data.get(account) { + if data.lamports() == 0 { + None + } else { + owners.iter().position(|entry| data.owner() == entry) + } + } else { + None + } + } + + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option { + self.account_shared_data.get(pubkey).cloned() + } + + fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64) { + (Hash::new_unique(), 2) + } + + fn get_rent_collector(&self) -> &RentCollector { + &self.rent_collector + } + + fn get_feature_set(&self) -> Arc { + self.feature_set.clone() + } + } + + #[test] + fn test_load_program_accounts_account_not_found() { + let mut mock_bank = MockBankCallback::default(); + let key = Pubkey::new_unique(); + let environment = ProgramRuntimeEnvironments::default(); + + let result = load_program_accounts(&mock_bank, &key, &environment); + + assert!(matches!(result, ProgramAccountLoadResult::AccountNotFound)); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader_upgradeable::id()); + let state = UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }; + account_data.set_data(bincode::serialize(&state).unwrap()); + mock_bank + .account_shared_data + .insert(key, account_data.clone()); + + let result = load_program_accounts(&mock_bank, &key, &environment); + assert!(matches!(result, ProgramAccountLoadResult::AccountNotFound)); + + account_data.set_data(Vec::new()); + mock_bank.account_shared_data.insert(key, account_data); + + let result = load_program_accounts(&mock_bank, &key, &environment); + + assert!(matches!( + result, + ProgramAccountLoadResult::InvalidAccountData(_) + )); + } + + #[test] + fn test_load_program_accounts_loader_v4() { + let key = Pubkey::new_unique(); + let mut mock_bank = MockBankCallback::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_owner(loader_v4::id()); + let environment = ProgramRuntimeEnvironments::default(); + mock_bank + .account_shared_data + .insert(key, account_data.clone()); + + let result = load_program_accounts(&mock_bank, &key, &environment); + assert!(matches!( + result, + ProgramAccountLoadResult::InvalidAccountData(_) + )); + + account_data.set_data(vec![0; 64]); + mock_bank + .account_shared_data + .insert(key, account_data.clone()); + let result = load_program_accounts(&mock_bank, &key, &environment); + assert!(matches!( + result, + ProgramAccountLoadResult::InvalidAccountData(_) + )); + + let loader_data = LoaderV4State { + slot: 25, + authority_address: Pubkey::new_unique(), + status: LoaderV4Status::Deployed, + }; + let encoded = unsafe { + std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>( + &loader_data, + ) + }; + account_data.set_data(encoded.to_vec()); + mock_bank + .account_shared_data + .insert(key, account_data.clone()); + + let result = load_program_accounts(&mock_bank, &key, &environment); + + match result { + ProgramAccountLoadResult::ProgramOfLoaderV4(data, slot) => { + assert_eq!(data, account_data); + assert_eq!(slot, 25); + } + + _ => panic!("Invalid result"), + } + } + + #[test] + fn test_load_program_accounts_loader_v1_or_v2() { + let key = Pubkey::new_unique(); + let mut mock_bank = MockBankCallback::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader::id()); + let environment = ProgramRuntimeEnvironments::default(); + mock_bank + .account_shared_data + .insert(key, account_data.clone()); + + let result = load_program_accounts(&mock_bank, &key, &environment); + match result { + ProgramAccountLoadResult::ProgramOfLoaderV1orV2(data) => { + assert_eq!(data, account_data); + } + _ => panic!("Invalid result"), + } + } + + #[test] + fn test_load_program_accounts_success() { + let key1 = Pubkey::new_unique(); + let key2 = Pubkey::new_unique(); + let mut mock_bank = MockBankCallback::default(); + let environment = ProgramRuntimeEnvironments::default(); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader_upgradeable::id()); + + let state = UpgradeableLoaderState::Program { + programdata_address: key2, + }; + account_data.set_data(bincode::serialize(&state).unwrap()); + mock_bank + .account_shared_data + .insert(key1, account_data.clone()); + + let state = UpgradeableLoaderState::ProgramData { + slot: 25, + upgrade_authority_address: None, + }; + let mut account_data2 = AccountSharedData::default(); + account_data2.set_data(bincode::serialize(&state).unwrap()); + mock_bank + .account_shared_data + .insert(key2, account_data2.clone()); + + let result = load_program_accounts(&mock_bank, &key1, &environment); + + match result { + ProgramAccountLoadResult::ProgramOfLoaderV3(data1, data2, slot) => { + assert_eq!(data1, account_data); + assert_eq!(data2, account_data2); + assert_eq!(slot, 25); + } + + _ => panic!("Invalid result"), + } + } + + fn load_test_program() -> Vec { + let mut dir = env::current_dir().unwrap(); + dir.push("tests"); + dir.push("example-programs"); + dir.push("hello-solana"); + dir.push("hello_solana_program.so"); + let mut file = File::open(dir.clone()).expect("file not found"); + let metadata = fs::metadata(dir).expect("Unable to read metadata"); + let mut buffer = vec![0; metadata.len() as usize]; + file.read_exact(&mut buffer).expect("Buffer overflow"); + buffer + } + + #[test] + fn test_load_program_from_bytes() { + let buffer = load_test_program(); + + let mut metrics = LoadProgramMetrics::default(); + let loader = bpf_loader_upgradeable::id(); + let size = buffer.len(); + let slot = 2; + let environment = ProgramRuntimeEnvironment::new(BuiltinProgram::new_mock()); + + let result = load_program_from_bytes( + &mut metrics, + &buffer, + &loader, + size, + slot, + environment.clone(), + false, + ); + + assert!(result.is_ok()); + + let result = load_program_from_bytes( + &mut metrics, + &buffer, + &loader, + size, + slot, + environment, + true, + ); + + assert!(result.is_ok()); + } +} diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 21a568b5e25620..abe803af81cb88 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -5,6 +5,9 @@ use { }, account_overrides::AccountOverrides, message_processor::MessageProcessor, + program_loader::{ + load_program_accounts, load_program_from_bytes, ProgramAccountLoadResult, + }, transaction_account_state_info::TransactionAccountStateInfo, transaction_error_metrics::TransactionErrorMetrics, transaction_processing_callback::TransactionProcessingCallback, @@ -20,8 +23,7 @@ use { invoke_context::InvokeContext, loaded_programs::{ ForkGraph, LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, - LoadedProgramType, LoadedProgramsForTxBatch, ProgramCache, ProgramRuntimeEnvironment, - DELAY_VISIBILITY_SLOT_OFFSET, + LoadedProgramType, LoadedProgramsForTxBatch, ProgramCache, }, log_collector::LogCollector, runtime_config::RuntimeConfig, @@ -37,7 +39,7 @@ use { fee::FeeStructure, inner_instruction::{InnerInstruction, InnerInstructionsList}, instruction::{CompiledInstruction, InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT}, - loader_v4::{self, LoaderV4State, LoaderV4Status}, + loader_v4::{self, LoaderV4State}, message::SanitizedMessage, native_loader, pubkey::Pubkey, @@ -82,14 +84,6 @@ impl ExecutionRecordingConfig { } } -#[derive(Debug)] -enum ProgramAccountLoadResult { - InvalidAccountData, - ProgramOfLoaderV1orV2(AccountSharedData), - ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot), - ProgramOfLoaderV4(AccountSharedData, Slot), -} - #[derive(AbiExample)] pub struct TransactionBatchProcessor { /// Bank slot (i.e. block) @@ -375,14 +369,16 @@ impl TransactionBatchProcessor { ..LoadProgramMetrics::default() }; - let mut loaded_program = match self.load_program_accounts(callbacks, pubkey)? { - ProgramAccountLoadResult::InvalidAccountData => Ok(LoadedProgram::new_tombstone( + let mut loaded_program = match load_program_accounts(callbacks, pubkey, environments) { + ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone( self.slot, LoadedProgramType::Closed, )), + ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)), + ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => { - Self::load_program_from_bytes( + load_program_from_bytes( &mut load_program_metrics, program_account.data(), program_account.owner(), @@ -403,7 +399,7 @@ impl TransactionBatchProcessor { .get(UpgradeableLoaderState::size_of_programdata_metadata()..) .ok_or(Box::new(InstructionError::InvalidAccountData).into()) .and_then(|programdata| { - Self::load_program_from_bytes( + load_program_from_bytes( &mut load_program_metrics, programdata, program_account.owner(), @@ -423,7 +419,7 @@ impl TransactionBatchProcessor { .get(LoaderV4State::program_data_offset()..) .ok_or(Box::new(InstructionError::InvalidAccountData).into()) .and_then(|elf_bytes| { - Self::load_program_from_bytes( + load_program_from_bytes( &mut load_program_metrics, elf_bytes, &loader_v4::id(), @@ -771,89 +767,6 @@ impl TransactionBatchProcessor { } } - fn load_program_from_bytes( - load_program_metrics: &mut LoadProgramMetrics, - programdata: &[u8], - loader_key: &Pubkey, - account_size: usize, - deployment_slot: Slot, - program_runtime_environment: ProgramRuntimeEnvironment, - reloading: bool, - ) -> std::result::Result> { - if reloading { - // Safety: this is safe because the program is being reloaded in the cache. - unsafe { - LoadedProgram::reload( - loader_key, - program_runtime_environment.clone(), - deployment_slot, - deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), - programdata, - account_size, - load_program_metrics, - ) - } - } else { - LoadedProgram::new( - loader_key, - program_runtime_environment.clone(), - deployment_slot, - deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), - programdata, - account_size, - load_program_metrics, - ) - } - } - - fn load_program_accounts( - &self, - callbacks: &CB, - pubkey: &Pubkey, - ) -> Option { - let program_account = callbacks.get_account_shared_data(pubkey)?; - - if loader_v4::check_id(program_account.owner()) { - return Some( - solana_loader_v4_program::get_state(program_account.data()) - .ok() - .and_then(|state| { - (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot) - }) - .map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot)) - .unwrap_or(ProgramAccountLoadResult::InvalidAccountData), - ); - } - - if !bpf_loader_upgradeable::check_id(program_account.owner()) { - return Some(ProgramAccountLoadResult::ProgramOfLoaderV1orV2( - program_account, - )); - } - - if let Ok(UpgradeableLoaderState::Program { - programdata_address, - }) = program_account.state() - { - if let Some(programdata_account) = - callbacks.get_account_shared_data(&programdata_address) - { - if let Ok(UpgradeableLoaderState::ProgramData { - slot, - upgrade_authority_address: _, - }) = programdata_account.state() - { - return Some(ProgramAccountLoadResult::ProgramOfLoaderV3( - program_account, - programdata_account, - slot, - )); - } - } - } - Some(ProgramAccountLoadResult::InvalidAccountData) - } - /// Extract the InnerInstructionsList from a TransactionContext fn inner_instructions_list_from_instruction_trace( transaction_context: &TransactionContext, @@ -932,16 +845,14 @@ impl TransactionBatchProcessor { mod tests { use { super::*, - solana_program_runtime::{ - loaded_programs::{BlockRelation, ProgramRuntimeEnvironments}, - solana_rbpf::program::BuiltinProgram, - }, + solana_program_runtime::loaded_programs::{BlockRelation, ProgramRuntimeEnvironments}, solana_sdk::{ account::{create_account_shared_data_for_test, WritableAccount}, bpf_loader, feature_set::FeatureSet, fee_calculator::FeeCalculator, hash::Hash, + loader_v4::LoaderV4Status, message::{LegacyMessage, Message, MessageHeader}, rent_collector::RentCollector, rent_debits::RentDebits, @@ -1050,157 +961,6 @@ mod tests { ); } - #[test] - fn test_load_program_accounts_account_not_found() { - let mut mock_bank = MockBankCallback::default(); - let key = Pubkey::new_unique(); - let batch_processor = TransactionBatchProcessor::::default(); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - assert!(result.is_none()); - - let mut account_data = AccountSharedData::default(); - account_data.set_owner(bpf_loader_upgradeable::id()); - let state = UpgradeableLoaderState::Program { - programdata_address: Pubkey::new_unique(), - }; - account_data.set_data(bincode::serialize(&state).unwrap()); - mock_bank - .account_shared_data - .insert(key, account_data.clone()); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - assert!(matches!( - result, - Some(ProgramAccountLoadResult::InvalidAccountData) - )); - - account_data.set_data(Vec::new()); - mock_bank.account_shared_data.insert(key, account_data); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - - assert!(matches!( - result, - Some(ProgramAccountLoadResult::InvalidAccountData) - )); - } - - #[test] - fn test_load_program_accounts_loader_v4() { - let key = Pubkey::new_unique(); - let mut mock_bank = MockBankCallback::default(); - let mut account_data = AccountSharedData::default(); - account_data.set_owner(loader_v4::id()); - let batch_processor = TransactionBatchProcessor::::default(); - mock_bank - .account_shared_data - .insert(key, account_data.clone()); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - assert!(matches!( - result, - Some(ProgramAccountLoadResult::InvalidAccountData) - )); - - account_data.set_data(vec![0; 64]); - mock_bank - .account_shared_data - .insert(key, account_data.clone()); - let result = batch_processor.load_program_accounts(&mock_bank, &key); - assert!(matches!( - result, - Some(ProgramAccountLoadResult::InvalidAccountData) - )); - - let loader_data = LoaderV4State { - slot: 25, - authority_address: Pubkey::new_unique(), - status: LoaderV4Status::Deployed, - }; - let encoded = unsafe { - std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>( - &loader_data, - ) - }; - account_data.set_data(encoded.to_vec()); - mock_bank - .account_shared_data - .insert(key, account_data.clone()); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - - match result { - Some(ProgramAccountLoadResult::ProgramOfLoaderV4(data, slot)) => { - assert_eq!(data, account_data); - assert_eq!(slot, 25); - } - - _ => panic!("Invalid result"), - } - } - - #[test] - fn test_load_program_accounts_loader_v1_or_v2() { - let key = Pubkey::new_unique(); - let mut mock_bank = MockBankCallback::default(); - let mut account_data = AccountSharedData::default(); - account_data.set_owner(bpf_loader::id()); - let batch_processor = TransactionBatchProcessor::::default(); - mock_bank - .account_shared_data - .insert(key, account_data.clone()); - - let result = batch_processor.load_program_accounts(&mock_bank, &key); - match result { - Some(ProgramAccountLoadResult::ProgramOfLoaderV1orV2(data)) => { - assert_eq!(data, account_data); - } - _ => panic!("Invalid result"), - } - } - - #[test] - fn test_load_program_accounts_success() { - let key1 = Pubkey::new_unique(); - let key2 = Pubkey::new_unique(); - let mut mock_bank = MockBankCallback::default(); - let batch_processor = TransactionBatchProcessor::::default(); - - let mut account_data = AccountSharedData::default(); - account_data.set_owner(bpf_loader_upgradeable::id()); - - let state = UpgradeableLoaderState::Program { - programdata_address: key2, - }; - account_data.set_data(bincode::serialize(&state).unwrap()); - mock_bank - .account_shared_data - .insert(key1, account_data.clone()); - - let state = UpgradeableLoaderState::ProgramData { - slot: 25, - upgrade_authority_address: None, - }; - let mut account_data2 = AccountSharedData::default(); - account_data2.set_data(bincode::serialize(&state).unwrap()); - mock_bank - .account_shared_data - .insert(key2, account_data2.clone()); - - let result = batch_processor.load_program_accounts(&mock_bank, &key1); - - match result { - Some(ProgramAccountLoadResult::ProgramOfLoaderV3(data1, data2, slot)) => { - assert_eq!(data1, account_data); - assert_eq!(data2, account_data2); - assert_eq!(slot, 25); - } - - _ => panic!("Invalid result"), - } - } - fn load_test_program() -> Vec { let mut dir = env::current_dir().unwrap(); dir.push("tests"); @@ -1214,41 +974,6 @@ mod tests { buffer } - #[test] - fn test_load_program_from_bytes() { - let buffer = load_test_program(); - - let mut metrics = LoadProgramMetrics::default(); - let loader = bpf_loader_upgradeable::id(); - let size = buffer.len(); - let slot = 2; - let environment = ProgramRuntimeEnvironment::new(BuiltinProgram::new_mock()); - - let result = TransactionBatchProcessor::::load_program_from_bytes( - &mut metrics, - &buffer, - &loader, - size, - slot, - environment.clone(), - false, - ); - - assert!(result.is_ok()); - - let result = TransactionBatchProcessor::::load_program_from_bytes( - &mut metrics, - &buffer, - &loader, - size, - slot, - environment, - true, - ); - - assert!(result.is_ok()); - } - #[test] fn test_load_program_not_found() { let mock_bank = MockBankCallback::default(); @@ -1324,7 +1049,7 @@ mod tests { let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let environments = ProgramRuntimeEnvironments::default(); - let expected = TransactionBatchProcessor::::load_program_from_bytes( + let expected = load_program_from_bytes( &mut LoadProgramMetrics::default(), account_data.data(), account_data.owner(), @@ -1405,7 +1130,7 @@ mod tests { .set_data(data[UpgradeableLoaderState::size_of_programdata_metadata()..].to_vec()); let environments = ProgramRuntimeEnvironments::default(); - let expected = TransactionBatchProcessor::::load_program_from_bytes( + let expected = load_program_from_bytes( &mut LoadProgramMetrics::default(), account_data.data(), account_data.owner(), @@ -1477,7 +1202,7 @@ mod tests { .insert(key, account_data.clone()); let environments = ProgramRuntimeEnvironments::default(); - let expected = TransactionBatchProcessor::::load_program_from_bytes( + let expected = load_program_from_bytes( &mut LoadProgramMetrics::default(), account_data.data(), account_data.owner(),