From 2dba7f988456a500e8d6b67e6a9e0f514785489a Mon Sep 17 00:00:00 2001 From: Lucas Steuernagel Date: Thu, 15 Aug 2024 11:29:51 -0300 Subject: [PATCH 1/2] Implement concurrent test for the SVM --- svm/tests/concurrent_tests.rs | 201 +++++++++++++++++- .../transfer-from-account/Cargo.toml | 12 ++ .../transfer-from-account/src/lib.rs | 28 +++ .../transfer_from_account_program.so | Bin 0 -> 67888 bytes svm/tests/integration_test.rs | 180 +--------------- svm/tests/mock_bank.rs | 163 +++++++++++++- 6 files changed, 404 insertions(+), 180 deletions(-) create mode 100644 svm/tests/example-programs/transfer-from-account/Cargo.toml create mode 100644 svm/tests/example-programs/transfer-from-account/src/lib.rs create mode 100755 svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so diff --git a/svm/tests/concurrent_tests.rs b/svm/tests/concurrent_tests.rs index cfe9f2233afceb..308c0ae07ddbac 100644 --- a/svm/tests/concurrent_tests.rs +++ b/svm/tests/concurrent_tests.rs @@ -1,20 +1,40 @@ #![cfg(feature = "shuttle-test")] use { - crate::mock_bank::{deploy_program, MockForkGraph}, + crate::{ + mock_bank::{ + create_executable_environment, deploy_program, register_builtins, MockForkGraph, + }, + transaction_builder::SanitizedTransactionBuilder, + }, mock_bank::MockBankCallback, shuttle::{ sync::{Arc, RwLock}, thread, Runner, }, solana_program_runtime::loaded_programs::ProgramCacheEntryType, - solana_sdk::pubkey::Pubkey, - solana_svm::transaction_processor::TransactionBatchProcessor, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + hash::Hash, + instruction::AccountMeta, + pubkey::Pubkey, + signature::Signature, + }, + solana_svm::{ + account_loader::{CheckedTransactionDetails, TransactionCheckResult}, + transaction_processing_result::TransactionProcessingResultExtensions, + transaction_processor::{ + ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig, + TransactionProcessingEnvironment, + }, + }, std::collections::{HashMap, HashSet}, }; mod mock_bank; +mod transaction_builder; + fn program_cache_execution(threads: usize) { let mut mock_bank = MockBankCallback::default(); let batch_processor = TransactionBatchProcessor::::new(5, 5, HashSet::new()); @@ -97,3 +117,178 @@ fn test_program_cache_with_exhaustive_scheduler() { let runner = Runner::new(scheduler, Default::default()); runner.run(move || program_cache_execution(4)); } + +// This test executes multiple transactions in parallel where all read from the same data account, +// but write to different accounts. Given that there are no locks in this case, SVM must behave +// correctly. +fn svm_concurrent() { + let mock_bank = Arc::new(MockBankCallback::default()); + let batch_processor = Arc::new(TransactionBatchProcessor::::new( + 5, + 2, + HashSet::new(), + )); + let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); + + create_executable_environment( + fork_graph.clone(), + &mock_bank, + &mut batch_processor.program_cache.write().unwrap(), + ); + + batch_processor.fill_missing_sysvar_cache_entries(&*mock_bank); + register_builtins(&mock_bank, &batch_processor); + + let mut transaction_builder = SanitizedTransactionBuilder::default(); + let program_id = deploy_program("transfer-from-account".to_string(), 0, &mock_bank); + + const THREADS: usize = 4; + const TRANSACTIONS_PER_THREAD: usize = 3; + const AMOUNT: u64 = 50; + const CAPACITY: usize = THREADS * TRANSACTIONS_PER_THREAD; + const BALANCE: u64 = 500000; + + let mut transactions = vec![Vec::new(); THREADS]; + let mut check_data = vec![Vec::new(); THREADS]; + let read_account = Pubkey::new_unique(); + let mut account_data = AccountSharedData::default(); + account_data.set_data(AMOUNT.to_le_bytes().to_vec()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(read_account, account_data); + + #[derive(Clone)] + struct CheckTxData { + sender: Pubkey, + recipient: Pubkey, + fee_payer: Pubkey, + } + + for idx in 0..CAPACITY { + let sender = Pubkey::new_unique(); + let recipient = Pubkey::new_unique(); + let fee_payer = Pubkey::new_unique(); + let system_account = Pubkey::from([0u8; 32]); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(BALANCE); + + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sender, account_data.clone()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(recipient, account_data.clone()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(fee_payer, account_data); + + transaction_builder.create_instruction( + program_id, + vec![ + AccountMeta { + pubkey: sender, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: recipient, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: read_account, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: system_account, + is_signer: false, + is_writable: false, + }, + ], + HashMap::from([(sender, Signature::new_unique())]), + vec![0], + ); + + let sanitized_transaction = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), true); + transactions[idx % THREADS].push(sanitized_transaction.unwrap()); + check_data[idx % THREADS].push(CheckTxData { + fee_payer, + recipient, + sender, + }); + } + + let ths: Vec<_> = (0..THREADS) + .map(|idx| { + let local_batch = batch_processor.clone(); + let local_bank = mock_bank.clone(); + let th_txs = std::mem::take(&mut transactions[idx]); + let check_results = vec![ + Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 20 + }) as TransactionCheckResult; + TRANSACTIONS_PER_THREAD + ]; + let processing_config = TransactionProcessingConfig { + recording_config: ExecutionRecordingConfig { + enable_log_recording: true, + enable_return_data_recording: false, + enable_cpi_recording: false, + }, + ..Default::default() + }; + let check_tx_data = std::mem::take(&mut check_data[idx]); + + thread::spawn(move || { + let result = local_batch.load_and_execute_sanitized_transactions( + &*local_bank, + &th_txs, + check_results, + &TransactionProcessingEnvironment::default(), + &processing_config, + ); + + for (idx, item) in result.processing_results.iter().enumerate() { + assert!(item.was_processed()); + let inserted_accounts = &check_tx_data[idx]; + for (key, account_data) in &item.as_ref().unwrap().loaded_transaction.accounts { + if *key == inserted_accounts.fee_payer { + assert_eq!(account_data.lamports(), BALANCE - 10000); + } else if *key == inserted_accounts.sender { + assert_eq!(account_data.lamports(), BALANCE - AMOUNT); + } else if *key == inserted_accounts.recipient { + assert_eq!(account_data.lamports(), BALANCE + AMOUNT); + } + } + } + }) + }) + .collect(); + + for th in ths { + th.join().unwrap(); + } +} + +#[test] +fn test_svm_with_probabilistic_scheduler() { + shuttle::check_pct( + move || { + svm_concurrent(); + }, + 300, + 5, + ); +} diff --git a/svm/tests/example-programs/transfer-from-account/Cargo.toml b/svm/tests/example-programs/transfer-from-account/Cargo.toml new file mode 100644 index 00000000000000..3b05d81523837c --- /dev/null +++ b/svm/tests/example-programs/transfer-from-account/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "transfer-from-account" +version = "2.1.0" +edition = "2021" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=2.1.0" } + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] \ No newline at end of file diff --git a/svm/tests/example-programs/transfer-from-account/src/lib.rs b/svm/tests/example-programs/transfer-from-account/src/lib.rs new file mode 100644 index 00000000000000..08494a13ce0395 --- /dev/null +++ b/svm/tests/example-programs/transfer-from-account/src/lib.rs @@ -0,0 +1,28 @@ +use solana_program::{ + account_info::{AccountInfo, next_account_info}, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, + program::invoke, system_instruction, +}; + +entrypoint!(process_instruction); + + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _data: &[u8] +) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let payer = next_account_info(accounts_iter)?; + let recipient = next_account_info(accounts_iter)?; + let data_account = next_account_info(accounts_iter)?; + let system_program = next_account_info(accounts_iter)?; + + let amount = u64::from_le_bytes(data_account.data.borrow()[0..8].try_into().unwrap()); + + invoke( + &system_instruction::transfer(payer.key, recipient.key, amount), + &[payer.clone(), recipient.clone(), system_program.clone()], + )?; + + Ok(()) +} \ No newline at end of file diff --git a/svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so b/svm/tests/example-programs/transfer-from-account/transfer_from_account_program.so new file mode 100755 index 0000000000000000000000000000000000000000..a3ef926d3747ef4a499aa4dd4127fcadbead6948 GIT binary patch literal 67888 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 literal 0 HcmV?d00001 diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 5070bca08e0907..f35f023e54ae2a 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -2,28 +2,15 @@ use { crate::{ - mock_bank::{deploy_program, MockBankCallback}, - transaction_builder::SanitizedTransactionBuilder, - }, - solana_bpf_loader_program::syscalls::{ - SyscallAbort, SyscallGetClockSysvar, SyscallInvokeSignedRust, SyscallLog, SyscallMemcpy, - SyscallMemset, SyscallSetReturnData, - }, - solana_compute_budget::compute_budget::ComputeBudget, - solana_program_runtime::{ - invoke_context::InvokeContext, - loaded_programs::{ - BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramRuntimeEnvironments, - }, - solana_rbpf::{ - program::{BuiltinFunction, BuiltinProgram, FunctionRegistry}, - vm::Config, + mock_bank::{ + create_executable_environment, deploy_program, register_builtins, MockBankCallback, + MockForkGraph, }, + transaction_builder::SanitizedTransactionBuilder, }, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, - bpf_loader_upgradeable::{self}, - clock::{Clock, Epoch, Slot, UnixTimestamp}, + clock::Clock, hash::Hash, instruction::AccountMeta, pubkey::Pubkey, @@ -40,165 +27,20 @@ use { TransactionProcessingEnvironment, }, }, - std::{ - cmp::Ordering, - collections::{HashMap, HashSet}, - sync::{Arc, RwLock}, - time::{SystemTime, UNIX_EPOCH}, - }, + solana_type_overrides::sync::{Arc, RwLock}, + std::collections::{HashMap, HashSet}, }; // This module contains the implementation of TransactionProcessingCallback mod mock_bank; mod transaction_builder; -const BPF_LOADER_NAME: &str = "solana_bpf_loader_upgradeable_program"; -const SYSTEM_PROGRAM_NAME: &str = "system_program"; const DEPLOYMENT_SLOT: u64 = 0; const EXECUTION_SLOT: u64 = 5; // The execution slot must be greater than the deployment slot -const DEPLOYMENT_EPOCH: u64 = 0; const EXECUTION_EPOCH: u64 = 2; // The execution epoch must be greater than the deployment epoch -struct MockForkGraph {} - -impl ForkGraph for MockForkGraph { - fn relationship(&self, a: Slot, b: Slot) -> BlockRelation { - match a.cmp(&b) { - Ordering::Less => BlockRelation::Ancestor, - Ordering::Equal => BlockRelation::Equal, - Ordering::Greater => BlockRelation::Descendant, - } - } - - fn slot_epoch(&self, _slot: Slot) -> Option { - Some(0) - } -} - -fn create_custom_environment<'a>() -> BuiltinProgram> { - let compute_budget = ComputeBudget::default(); - let vm_config = Config { - max_call_depth: compute_budget.max_call_depth, - stack_frame_size: compute_budget.stack_frame_size, - enable_address_translation: true, - enable_stack_frame_gaps: true, - instruction_meter_checkpoint_distance: 10000, - enable_instruction_meter: true, - enable_instruction_tracing: true, - enable_symbol_and_section_labels: true, - reject_broken_elfs: true, - noop_instruction_rate: 256, - sanitize_user_provided_values: true, - external_internal_function_hash_collision: false, - reject_callx_r10: false, - enable_sbpf_v1: true, - enable_sbpf_v2: false, - optimize_rodata: false, - aligned_memory_mapping: true, - }; - - // These functions are system calls the compile contract calls during execution, so they - // need to be registered. - let mut function_registry = FunctionRegistry::>::default(); - function_registry - .register_function_hashed(*b"abort", SyscallAbort::vm) - .expect("Registration failed"); - function_registry - .register_function_hashed(*b"sol_log_", SyscallLog::vm) - .expect("Registration failed"); - function_registry - .register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::vm) - .expect("Registration failed"); - function_registry - .register_function_hashed(*b"sol_memset_", SyscallMemset::vm) - .expect("Registration failed"); - - function_registry - .register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::vm) - .expect("Registration failed"); - - function_registry - .register_function_hashed(*b"sol_set_return_data", SyscallSetReturnData::vm) - .expect("Registration failed"); - - function_registry - .register_function_hashed(*b"sol_get_clock_sysvar", SyscallGetClockSysvar::vm) - .expect("Registration failed"); - - BuiltinProgram::new_loader(vm_config, function_registry) -} - -fn create_executable_environment( - fork_graph: Arc>, - mock_bank: &mut MockBankCallback, - program_cache: &mut ProgramCache, -) { - program_cache.environments = ProgramRuntimeEnvironments { - program_runtime_v1: Arc::new(create_custom_environment()), - // We are not using program runtime v2 - program_runtime_v2: Arc::new(BuiltinProgram::new_loader( - Config::default(), - FunctionRegistry::default(), - )), - }; - - program_cache.fork_graph = Some(Arc::downgrade(&fork_graph)); - - // We must fill in the sysvar cache entries - let time_now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() as i64; - let clock = Clock { - slot: DEPLOYMENT_SLOT, - epoch_start_timestamp: time_now.saturating_sub(10) as UnixTimestamp, - epoch: DEPLOYMENT_EPOCH, - leader_schedule_epoch: DEPLOYMENT_EPOCH, - unix_timestamp: time_now as UnixTimestamp, - }; - - let mut account_data = AccountSharedData::default(); - account_data.set_data(bincode::serialize(&clock).unwrap()); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(Clock::id(), account_data); -} - -fn register_builtins( - mock_bank: &MockBankCallback, - batch_processor: &TransactionBatchProcessor, -) { - // We must register the bpf loader account as a loadable account, otherwise programs - // won't execute. - batch_processor.add_builtin( - mock_bank, - bpf_loader_upgradeable::id(), - BPF_LOADER_NAME, - ProgramCacheEntry::new_builtin( - DEPLOYMENT_SLOT, - BPF_LOADER_NAME.len(), - solana_bpf_loader_program::Entrypoint::vm, - ), - ); - - // In order to perform a transference of native tokens using the system instruction, - // the system program builtin must be registered. - batch_processor.add_builtin( - mock_bank, - solana_system_program::id(), - SYSTEM_PROGRAM_NAME, - ProgramCacheEntry::new_builtin( - DEPLOYMENT_SLOT, - SYSTEM_PROGRAM_NAME.len(), - solana_system_program::system_processor::Entrypoint::vm, - ), - ); -} - fn prepare_transactions( - mock_bank: &mut MockBankCallback, + mock_bank: &MockBankCallback, ) -> (Vec, Vec) { let mut transaction_builder = SanitizedTransactionBuilder::default(); let mut all_transactions = Vec::new(); @@ -392,8 +234,8 @@ fn prepare_transactions( #[test] fn svm_integration() { - let mut mock_bank = MockBankCallback::default(); - let (transactions, check_results) = prepare_transactions(&mut mock_bank); + let mock_bank = MockBankCallback::default(); + let (transactions, check_results) = prepare_transactions(&mock_bank); let batch_processor = TransactionBatchProcessor::::new( EXECUTION_SLOT, EXECUTION_EPOCH, @@ -404,7 +246,7 @@ fn svm_integration() { create_executable_environment( fork_graph.clone(), - &mut mock_bank, + &mock_bank, &mut batch_processor.program_cache.write().unwrap(), ); diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 355f9f0ce8898a..03405dcaa6ec15 100644 --- a/svm/tests/mock_bank.rs +++ b/svm/tests/mock_bank.rs @@ -1,15 +1,33 @@ use { - solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph}, + solana_bpf_loader_program::syscalls::{ + SyscallAbort, SyscallGetClockSysvar, SyscallInvokeSignedRust, SyscallLog, SyscallMemcpy, + SyscallMemset, SyscallSetReturnData, + }, + solana_compute_budget::compute_budget::ComputeBudget, + solana_program_runtime::{ + invoke_context::InvokeContext, + loaded_programs::{ + BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramRuntimeEnvironments, + }, + solana_rbpf::{ + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry}, + vm::Config, + }, + }, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - clock::Epoch, + clock::{Clock, Epoch, UnixTimestamp}, feature_set::FeatureSet, native_loader, pubkey::Pubkey, slot_hashes::Slot, + sysvar::SysvarId, + }, + solana_svm::{ + transaction_processing_callback::TransactionProcessingCallback, + transaction_processor::TransactionBatchProcessor, }, - solana_svm::transaction_processing_callback::TransactionProcessingCallback, solana_type_overrides::sync::{Arc, RwLock}, std::{ cmp::Ordering, @@ -17,6 +35,7 @@ use { env, fs::{self, File}, io::Read, + time::{SystemTime, UNIX_EPOCH}, }, }; @@ -89,6 +108,7 @@ fn load_program(name: String) -> Vec { dir.push(name.as_str()); let name = name.replace('-', "_"); dir.push(name + "_program.so"); + std::println!("The dir is: {:?}", dir); 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]; @@ -97,11 +117,7 @@ fn load_program(name: String) -> Vec { } #[allow(unused)] -pub fn deploy_program( - name: String, - deployment_slot: Slot, - mock_bank: &mut MockBankCallback, -) -> Pubkey { +pub fn deploy_program(name: String, deployment_slot: Slot, mock_bank: &MockBankCallback) -> Pubkey { let program_account = Pubkey::new_unique(); let program_data_account = Pubkey::new_unique(); let state = UpgradeableLoaderState::Program { @@ -144,3 +160,134 @@ pub fn deploy_program( program_account } + +#[allow(unused)] +pub fn create_executable_environment( + fork_graph: Arc>, + mock_bank: &MockBankCallback, + program_cache: &mut ProgramCache, +) { + const DEPLOYMENT_EPOCH: u64 = 0; + const DEPLOYMENT_SLOT: u64 = 0; + + program_cache.environments = ProgramRuntimeEnvironments { + program_runtime_v1: Arc::new(create_custom_environment()), + // We are not using program runtime v2 + program_runtime_v2: Arc::new(BuiltinProgram::new_loader( + Config::default(), + FunctionRegistry::default(), + )), + }; + + program_cache.fork_graph = Some(Arc::downgrade(&fork_graph)); + + // We must fill in the sysvar cache entries + let time_now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64; + let clock = Clock { + slot: DEPLOYMENT_SLOT, + epoch_start_timestamp: time_now.saturating_sub(10) as UnixTimestamp, + epoch: DEPLOYMENT_EPOCH, + leader_schedule_epoch: DEPLOYMENT_EPOCH, + unix_timestamp: time_now as UnixTimestamp, + }; + + let mut account_data = AccountSharedData::default(); + account_data.set_data(bincode::serialize(&clock).unwrap()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(Clock::id(), account_data); +} + +#[allow(unused)] +pub fn register_builtins( + mock_bank: &MockBankCallback, + batch_processor: &TransactionBatchProcessor, +) { + const DEPLOYMENT_SLOT: u64 = 0; + // We must register the bpf loader account as a loadable account, otherwise programs + // won't execute. + let bpf_loader_name = "solana_bpf_loader_upgradeable_program"; + batch_processor.add_builtin( + mock_bank, + bpf_loader_upgradeable::id(), + bpf_loader_name, + ProgramCacheEntry::new_builtin( + DEPLOYMENT_SLOT, + bpf_loader_name.len(), + solana_bpf_loader_program::Entrypoint::vm, + ), + ); + + // In order to perform a transference of native tokens using the system instruction, + // the system program builtin must be registered. + let system_program_name = "system_program"; + batch_processor.add_builtin( + mock_bank, + solana_system_program::id(), + system_program_name, + ProgramCacheEntry::new_builtin( + DEPLOYMENT_SLOT, + system_program_name.len(), + solana_system_program::system_processor::Entrypoint::vm, + ), + ); +} + +#[allow(unused)] +fn create_custom_environment<'a>() -> BuiltinProgram> { + let compute_budget = ComputeBudget::default(); + let vm_config = Config { + max_call_depth: compute_budget.max_call_depth, + stack_frame_size: compute_budget.stack_frame_size, + enable_address_translation: true, + enable_stack_frame_gaps: true, + instruction_meter_checkpoint_distance: 10000, + enable_instruction_meter: true, + enable_instruction_tracing: true, + enable_symbol_and_section_labels: true, + reject_broken_elfs: true, + noop_instruction_rate: 256, + sanitize_user_provided_values: true, + external_internal_function_hash_collision: false, + reject_callx_r10: false, + enable_sbpf_v1: true, + enable_sbpf_v2: false, + optimize_rodata: false, + aligned_memory_mapping: true, + }; + + // These functions are system calls the compile contract calls during execution, so they + // need to be registered. + let mut function_registry = FunctionRegistry::>::default(); + function_registry + .register_function_hashed(*b"abort", SyscallAbort::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_log_", SyscallLog::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_memset_", SyscallMemset::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_set_return_data", SyscallSetReturnData::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_get_clock_sysvar", SyscallGetClockSysvar::vm) + .expect("Registration failed"); + + BuiltinProgram::new_loader(vm_config, function_registry) +} From 6dd2c724f79aedc26bc87fb404ed5ec0be5cb355 Mon Sep 17 00:00:00 2001 From: Lucas Steuernagel Date: Fri, 16 Aug 2024 09:36:59 -0300 Subject: [PATCH 2/2] Remove debug print and optimize lock --- svm/tests/concurrent_tests.rs | 21 ++++++--------------- svm/tests/mock_bank.rs | 1 - 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/svm/tests/concurrent_tests.rs b/svm/tests/concurrent_tests.rs index 308c0ae07ddbac..e11019be16a346 100644 --- a/svm/tests/concurrent_tests.rs +++ b/svm/tests/concurrent_tests.rs @@ -175,21 +175,12 @@ fn svm_concurrent() { let mut account_data = AccountSharedData::default(); account_data.set_lamports(BALANCE); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(sender, account_data.clone()); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(recipient, account_data.clone()); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(fee_payer, account_data); + { + let shared_data = &mut mock_bank.account_shared_data.write().unwrap(); + shared_data.insert(sender, account_data.clone()); + shared_data.insert(recipient, account_data.clone()); + shared_data.insert(fee_payer, account_data); + } transaction_builder.create_instruction( program_id, diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 03405dcaa6ec15..169ac63cf8854b 100644 --- a/svm/tests/mock_bank.rs +++ b/svm/tests/mock_bank.rs @@ -108,7 +108,6 @@ fn load_program(name: String) -> Vec { dir.push(name.as_str()); let name = name.replace('-', "_"); dir.push(name + "_program.so"); - std::println!("The dir is: {:?}", dir); 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];