concurrent_tests.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #![cfg(feature = "shuttle-test")]
  2. use {
  3. crate::mock_bank::{create_custom_loader, deploy_program, register_builtins, MockForkGraph},
  4. assert_matches::assert_matches,
  5. mock_bank::MockBankCallback,
  6. shuttle::{
  7. sync::{Arc, RwLock},
  8. thread, Runner,
  9. },
  10. solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
  11. solana_instruction::{AccountMeta, Instruction},
  12. solana_program_runtime::{
  13. execution_budget::SVMTransactionExecutionAndFeeBudgetLimits,
  14. loaded_programs::{ProgramCacheEntryType, ProgramCacheForTxBatch},
  15. },
  16. solana_pubkey::Pubkey,
  17. solana_svm::{
  18. account_loader::{AccountLoader, CheckedTransactionDetails, TransactionCheckResult},
  19. transaction_processing_result::{
  20. ProcessedTransaction, TransactionProcessingResultExtensions,
  21. },
  22. transaction_processor::{
  23. ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig,
  24. TransactionProcessingEnvironment,
  25. },
  26. },
  27. solana_svm_feature_set::SVMFeatureSet,
  28. solana_svm_timings::ExecuteTimings,
  29. solana_transaction::{sanitized::SanitizedTransaction, Transaction},
  30. std::collections::HashSet,
  31. };
  32. mod mock_bank;
  33. const MAX_ITERATIONS: usize = 10_000;
  34. fn program_cache_execution(threads: usize) {
  35. let mut mock_bank = MockBankCallback::default();
  36. let fork_graph = Arc::new(RwLock::new(MockForkGraph {}));
  37. let batch_processor =
  38. TransactionBatchProcessor::new(5, 5, Arc::downgrade(&fork_graph), None, None);
  39. let programs = vec![
  40. deploy_program("hello-solana".to_string(), 0, &mut mock_bank),
  41. deploy_program("simple-transfer".to_string(), 0, &mut mock_bank),
  42. deploy_program("clock-sysvar".to_string(), 0, &mut mock_bank),
  43. ];
  44. let account_maps: HashSet<Pubkey> = programs.iter().copied().collect();
  45. let ths: Vec<_> = (0..threads)
  46. .map(|_| {
  47. let local_bank = mock_bank.clone();
  48. let processor = TransactionBatchProcessor::new_from(
  49. &batch_processor,
  50. batch_processor.slot,
  51. batch_processor.epoch,
  52. );
  53. let maps = account_maps.clone();
  54. let programs = programs.clone();
  55. thread::spawn(move || {
  56. let feature_set = SVMFeatureSet::all_enabled();
  57. let account_loader = AccountLoader::new_with_loaded_accounts_capacity(
  58. None,
  59. &local_bank,
  60. &feature_set,
  61. 0,
  62. );
  63. let mut result = ProgramCacheForTxBatch::new(processor.slot);
  64. let program_runtime_environments_for_execution =
  65. processor.get_environments_for_epoch(processor.epoch);
  66. processor.replenish_program_cache(
  67. &account_loader,
  68. &maps,
  69. &program_runtime_environments_for_execution,
  70. &mut result,
  71. &mut ExecuteTimings::default(),
  72. false,
  73. true,
  74. true,
  75. );
  76. for key in &programs {
  77. let cache_entry = result.find(key);
  78. assert!(matches!(
  79. cache_entry.unwrap().program,
  80. ProgramCacheEntryType::Loaded(_)
  81. ));
  82. }
  83. })
  84. })
  85. .collect();
  86. for th in ths {
  87. th.join().unwrap();
  88. }
  89. }
  90. // Shuttle has its own internal scheduler and the following tests change the way it operates to
  91. // increase the efficiency in finding problems in the program cache's concurrent code.
  92. // This test leverages the probabilistic concurrency testing algorithm
  93. // (https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/asplos277-pct.pdf).
  94. // It bounds the numbers of preemptions to explore (five in this test) for the four
  95. // threads we use. We run it for 300 iterations.
  96. #[test]
  97. fn test_program_cache_with_probabilistic_scheduler() {
  98. shuttle::check_pct(
  99. move || {
  100. program_cache_execution(4);
  101. },
  102. MAX_ITERATIONS,
  103. 5,
  104. );
  105. }
  106. // In this case, the scheduler is random and may preempt threads at any point and any time.
  107. #[test]
  108. fn test_program_cache_with_random_scheduler() {
  109. shuttle::check_random(move || program_cache_execution(4), MAX_ITERATIONS);
  110. }
  111. // This test explores all the possible thread scheduling patterns that might affect the program
  112. // cache. There is a limitation to run only 500 iterations to avoid consuming too much CI time.
  113. #[test]
  114. fn test_program_cache_with_exhaustive_scheduler() {
  115. // The DFS (shuttle::check_dfs) test is only complete when we do not generate random
  116. // values in a thread.
  117. // Since this is not the case for the execution of jitted program, we can still run the test
  118. // but with decreased accuracy.
  119. let scheduler = shuttle::scheduler::DfsScheduler::new(Some(MAX_ITERATIONS), true);
  120. let runner = Runner::new(scheduler, Default::default());
  121. runner.run(move || program_cache_execution(4));
  122. }
  123. // This test executes multiple transactions in parallel where all read from the same data account,
  124. // but write to different accounts. Given that there are no locks in this case, SVM must behave
  125. // correctly.
  126. fn svm_concurrent() {
  127. let mock_bank = Arc::new(MockBankCallback::default());
  128. let fork_graph = Arc::new(RwLock::new(MockForkGraph {}));
  129. let batch_processor = Arc::new(TransactionBatchProcessor::new(
  130. 5,
  131. 2,
  132. Arc::downgrade(&fork_graph),
  133. Some(Arc::new(create_custom_loader())),
  134. None, // We are not using program runtime v2.
  135. ));
  136. mock_bank.configure_sysvars();
  137. batch_processor.fill_missing_sysvar_cache_entries(&*mock_bank);
  138. register_builtins(&mock_bank, &batch_processor, false);
  139. let program_id = deploy_program("transfer-from-account".to_string(), 0, &mock_bank);
  140. const THREADS: usize = 4;
  141. const TRANSACTIONS_PER_THREAD: usize = 3;
  142. const AMOUNT: u64 = 50;
  143. const CAPACITY: usize = THREADS * TRANSACTIONS_PER_THREAD;
  144. const BALANCE: u64 = 500000;
  145. let mut transactions = vec![Vec::new(); THREADS];
  146. let mut check_data = vec![Vec::new(); THREADS];
  147. let read_account = Pubkey::new_unique();
  148. let mut account_data = AccountSharedData::default();
  149. account_data.set_data(AMOUNT.to_le_bytes().to_vec());
  150. account_data.set_rent_epoch(u64::MAX);
  151. account_data.set_lamports(1);
  152. mock_bank
  153. .account_shared_data
  154. .write()
  155. .unwrap()
  156. .insert(read_account, account_data);
  157. #[derive(Clone)]
  158. struct CheckTxData {
  159. sender: Pubkey,
  160. recipient: Pubkey,
  161. fee_payer: Pubkey,
  162. }
  163. for idx in 0..CAPACITY {
  164. let sender = Pubkey::new_unique();
  165. let recipient = Pubkey::new_unique();
  166. let fee_payer = Pubkey::new_unique();
  167. let system_account = Pubkey::from([0u8; 32]);
  168. let mut account_data = AccountSharedData::default();
  169. account_data.set_lamports(BALANCE);
  170. {
  171. let shared_data = &mut mock_bank.account_shared_data.write().unwrap();
  172. shared_data.insert(sender, account_data.clone());
  173. shared_data.insert(recipient, account_data.clone());
  174. shared_data.insert(fee_payer, account_data);
  175. }
  176. let accounts = vec![
  177. AccountMeta {
  178. pubkey: sender,
  179. is_signer: true,
  180. is_writable: true,
  181. },
  182. AccountMeta {
  183. pubkey: recipient,
  184. is_signer: false,
  185. is_writable: true,
  186. },
  187. AccountMeta {
  188. pubkey: read_account,
  189. is_signer: false,
  190. is_writable: false,
  191. },
  192. AccountMeta {
  193. pubkey: system_account,
  194. is_signer: false,
  195. is_writable: false,
  196. },
  197. ];
  198. let instruction = Instruction::new_with_bytes(program_id, &[0], accounts);
  199. let legacy_transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer));
  200. let sanitized_transaction =
  201. SanitizedTransaction::try_from_legacy_transaction(legacy_transaction, &HashSet::new());
  202. transactions[idx % THREADS].push(sanitized_transaction.unwrap());
  203. check_data[idx % THREADS].push(CheckTxData {
  204. fee_payer,
  205. recipient,
  206. sender,
  207. });
  208. }
  209. let ths: Vec<_> = (0..THREADS)
  210. .map(|idx| {
  211. let local_batch = batch_processor.clone();
  212. let local_bank = mock_bank.clone();
  213. let th_txs = std::mem::take(&mut transactions[idx]);
  214. let check_results = th_txs
  215. .iter()
  216. .map(|tx| {
  217. Ok(CheckedTransactionDetails::new(
  218. None,
  219. SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
  220. MockBankCallback::calculate_fee_details(tx, 0),
  221. ),
  222. )) as TransactionCheckResult
  223. })
  224. .collect();
  225. let processing_config = TransactionProcessingConfig {
  226. recording_config: ExecutionRecordingConfig {
  227. enable_log_recording: true,
  228. enable_return_data_recording: false,
  229. enable_cpi_recording: false,
  230. enable_transaction_balance_recording: false,
  231. },
  232. ..Default::default()
  233. };
  234. let check_tx_data = std::mem::take(&mut check_data[idx]);
  235. thread::spawn(move || {
  236. let result = local_batch.load_and_execute_sanitized_transactions(
  237. &*local_bank,
  238. &th_txs,
  239. check_results,
  240. &TransactionProcessingEnvironment {
  241. program_runtime_environments_for_execution: local_batch
  242. .environments
  243. .clone(),
  244. ..TransactionProcessingEnvironment::default()
  245. },
  246. &processing_config,
  247. );
  248. for (idx, processing_result) in result.processing_results.iter().enumerate() {
  249. assert!(processing_result.was_processed());
  250. let processed_tx = processing_result.processed_transaction().unwrap();
  251. assert_matches!(processed_tx, &ProcessedTransaction::Executed(_));
  252. let executed_tx = processed_tx.executed_transaction().unwrap();
  253. let inserted_accounts = &check_tx_data[idx];
  254. for (key, account_data) in &executed_tx.loaded_transaction.accounts {
  255. if *key == inserted_accounts.fee_payer {
  256. assert_eq!(account_data.lamports(), BALANCE - 10000);
  257. } else if *key == inserted_accounts.sender {
  258. assert_eq!(account_data.lamports(), BALANCE - AMOUNT);
  259. } else if *key == inserted_accounts.recipient {
  260. assert_eq!(account_data.lamports(), BALANCE + AMOUNT);
  261. }
  262. }
  263. }
  264. })
  265. })
  266. .collect();
  267. for th in ths {
  268. th.join().unwrap();
  269. }
  270. }
  271. #[test]
  272. fn test_svm_with_probabilistic_scheduler() {
  273. shuttle::check_pct(
  274. move || {
  275. svm_concurrent();
  276. },
  277. MAX_ITERATIONS,
  278. 5,
  279. );
  280. }