1use {
2 solana_account::WritableAccount,
3 solana_instructions_sysvar as instructions,
4 solana_measure::measure_us,
5 solana_precompiles::get_precompile,
6 solana_program_runtime::invoke_context::InvokeContext,
7 solana_svm_transaction::svm_message::SVMMessage,
8 solana_timings::{ExecuteDetailsTimings, ExecuteTimings},
9 solana_transaction_context::{IndexOfAccount, InstructionAccount},
10 solana_transaction_error::TransactionError,
11};
12
13pub(crate) fn process_message(
19 message: &impl SVMMessage,
20 program_indices: &[Vec<IndexOfAccount>],
21 invoke_context: &mut InvokeContext,
22 execute_timings: &mut ExecuteTimings,
23 accumulated_consumed_units: &mut u64,
24) -> Result<(), TransactionError> {
25 debug_assert_eq!(program_indices.len(), message.num_instructions());
26 for (instruction_index, ((program_id, instruction), program_indices)) in message
27 .program_instructions_iter()
28 .zip(program_indices.iter())
29 .enumerate()
30 {
31 if let Some(account_index) = invoke_context
34 .transaction_context
35 .find_index_of_account(&instructions::id())
36 {
37 let mut mut_account_ref = invoke_context
38 .transaction_context
39 .get_account_at_index(account_index)
40 .map_err(|_| TransactionError::InvalidAccountIndex)?
41 .borrow_mut();
42 instructions::store_current_index(
43 mut_account_ref.data_as_mut_slice(),
44 instruction_index as u16,
45 );
46 }
47
48 let mut instruction_accounts = Vec::with_capacity(instruction.accounts.len());
49 for (instruction_account_index, index_in_transaction) in
50 instruction.accounts.iter().enumerate()
51 {
52 let index_in_callee = instruction
53 .accounts
54 .get(0..instruction_account_index)
55 .ok_or(TransactionError::InvalidAccountIndex)?
56 .iter()
57 .position(|account_index| account_index == index_in_transaction)
58 .unwrap_or(instruction_account_index)
59 as IndexOfAccount;
60 let index_in_transaction = *index_in_transaction as usize;
61 instruction_accounts.push(InstructionAccount {
62 index_in_transaction: index_in_transaction as IndexOfAccount,
63 index_in_caller: index_in_transaction as IndexOfAccount,
64 index_in_callee,
65 is_signer: message.is_signer(index_in_transaction),
66 is_writable: message.is_writable(index_in_transaction),
67 });
68 }
69
70 let mut compute_units_consumed = 0;
71 let (result, process_instruction_us) = measure_us!({
72 if let Some(precompile) = get_precompile(program_id, |feature_id| {
73 invoke_context.get_feature_set().is_active(feature_id)
74 }) {
75 invoke_context.process_precompile(
76 precompile,
77 instruction.data,
78 &instruction_accounts,
79 program_indices,
80 message.instructions_iter().map(|ix| ix.data),
81 )
82 } else {
83 invoke_context.process_instruction(
84 instruction.data,
85 &instruction_accounts,
86 program_indices,
87 &mut compute_units_consumed,
88 execute_timings,
89 )
90 }
91 });
92
93 *accumulated_consumed_units =
94 accumulated_consumed_units.saturating_add(compute_units_consumed);
95 execute_timings.details.accumulate_program(
96 program_id,
97 process_instruction_us,
98 compute_units_consumed,
99 result.is_err(),
100 );
101 invoke_context.timings = {
102 execute_timings.details.accumulate(&invoke_context.timings);
103 ExecuteDetailsTimings::default()
104 };
105 execute_timings
106 .execute_accessories
107 .process_instructions
108 .total_us += process_instruction_us;
109
110 result.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
111 }
112 Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117 use {
118 super::*,
119 openssl::{
120 ec::{EcGroup, EcKey},
121 nid::Nid,
122 },
123 rand0_7::thread_rng,
124 solana_account::{AccountSharedData, ReadableAccount},
125 solana_compute_budget::compute_budget::ComputeBudget,
126 solana_ed25519_program::new_ed25519_instruction,
127 solana_feature_set::FeatureSet,
128 solana_hash::Hash,
129 solana_instruction::{error::InstructionError, AccountMeta, Instruction},
130 solana_message::{AccountKeys, Message, SanitizedMessage},
131 solana_program_runtime::{
132 declare_process_instruction,
133 invoke_context::EnvironmentConfig,
134 loaded_programs::{ProgramCacheEntry, ProgramCacheForTxBatch},
135 sysvar_cache::SysvarCache,
136 },
137 solana_pubkey::Pubkey,
138 solana_rent::Rent,
139 solana_reserved_account_keys::ReservedAccountKeys,
140 solana_sdk::native_loader::create_loadable_account_for_test,
141 solana_sdk_ids::{ed25519_program, native_loader, secp256k1_program, system_program},
142 solana_secp256k1_program::new_secp256k1_instruction,
143 solana_secp256r1_program::new_secp256r1_instruction,
144 solana_transaction_context::TransactionContext,
145 std::sync::Arc,
146 };
147
148 fn new_sanitized_message(message: Message) -> SanitizedMessage {
149 SanitizedMessage::try_from_legacy_message(message, &ReservedAccountKeys::empty_key_set())
150 .unwrap()
151 }
152
153 #[test]
154 fn test_process_message_readonly_handling() {
155 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
156 enum MockSystemInstruction {
157 Correct,
158 TransferLamports { lamports: u64 },
159 ChangeData { data: u8 },
160 }
161
162 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
163 let transaction_context = &invoke_context.transaction_context;
164 let instruction_context = transaction_context.get_current_instruction_context()?;
165 let instruction_data = instruction_context.get_instruction_data();
166 if let Ok(instruction) = bincode::deserialize(instruction_data) {
167 match instruction {
168 MockSystemInstruction::Correct => Ok(()),
169 MockSystemInstruction::TransferLamports { lamports } => {
170 instruction_context
171 .try_borrow_instruction_account(transaction_context, 0)?
172 .checked_sub_lamports(lamports)?;
173 instruction_context
174 .try_borrow_instruction_account(transaction_context, 1)?
175 .checked_add_lamports(lamports)?;
176 Ok(())
177 }
178 MockSystemInstruction::ChangeData { data } => {
179 instruction_context
180 .try_borrow_instruction_account(transaction_context, 1)?
181 .set_data(vec![data])?;
182 Ok(())
183 }
184 }
185 } else {
186 Err(InstructionError::InvalidInstructionData)
187 }
188 });
189
190 let writable_pubkey = Pubkey::new_unique();
191 let readonly_pubkey = Pubkey::new_unique();
192 let mock_system_program_id = Pubkey::new_unique();
193
194 let accounts = vec![
195 (
196 writable_pubkey,
197 AccountSharedData::new(100, 1, &mock_system_program_id),
198 ),
199 (
200 readonly_pubkey,
201 AccountSharedData::new(0, 1, &mock_system_program_id),
202 ),
203 (
204 mock_system_program_id,
205 create_loadable_account_for_test("mock_system_program"),
206 ),
207 ];
208 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3);
209 let program_indices = vec![vec![2]];
210 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
211 program_cache_for_tx_batch.replenish(
212 mock_system_program_id,
213 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
214 );
215 let account_keys = (0..transaction_context.get_number_of_accounts())
216 .map(|index| {
217 *transaction_context
218 .get_key_of_account_at_index(index)
219 .unwrap()
220 })
221 .collect::<Vec<_>>();
222 let account_metas = vec![
223 AccountMeta::new(writable_pubkey, true),
224 AccountMeta::new_readonly(readonly_pubkey, false),
225 ];
226
227 let message = new_sanitized_message(Message::new_with_compiled_instructions(
228 1,
229 0,
230 2,
231 account_keys.clone(),
232 Hash::default(),
233 AccountKeys::new(&account_keys, None).compile_instructions(&[
234 Instruction::new_with_bincode(
235 mock_system_program_id,
236 &MockSystemInstruction::Correct,
237 account_metas.clone(),
238 ),
239 ]),
240 ));
241 let sysvar_cache = SysvarCache::default();
242 let environment_config = EnvironmentConfig::new(
243 Hash::default(),
244 0,
245 0,
246 &|_| 0,
247 Arc::new(FeatureSet::all_enabled()),
248 &sysvar_cache,
249 );
250 let mut invoke_context = InvokeContext::new(
251 &mut transaction_context,
252 &mut program_cache_for_tx_batch,
253 environment_config,
254 None,
255 ComputeBudget::default(),
256 );
257 let result = process_message(
258 &message,
259 &program_indices,
260 &mut invoke_context,
261 &mut ExecuteTimings::default(),
262 &mut 0,
263 );
264 assert!(result.is_ok());
265 assert_eq!(
266 transaction_context
267 .get_account_at_index(0)
268 .unwrap()
269 .borrow()
270 .lamports(),
271 100
272 );
273 assert_eq!(
274 transaction_context
275 .get_account_at_index(1)
276 .unwrap()
277 .borrow()
278 .lamports(),
279 0
280 );
281
282 let message = new_sanitized_message(Message::new_with_compiled_instructions(
283 1,
284 0,
285 2,
286 account_keys.clone(),
287 Hash::default(),
288 AccountKeys::new(&account_keys, None).compile_instructions(&[
289 Instruction::new_with_bincode(
290 mock_system_program_id,
291 &MockSystemInstruction::TransferLamports { lamports: 50 },
292 account_metas.clone(),
293 ),
294 ]),
295 ));
296 let environment_config = EnvironmentConfig::new(
297 Hash::default(),
298 0,
299 0,
300 &|_| 0,
301 Arc::new(FeatureSet::all_enabled()),
302 &sysvar_cache,
303 );
304 let mut invoke_context = InvokeContext::new(
305 &mut transaction_context,
306 &mut program_cache_for_tx_batch,
307 environment_config,
308 None,
309 ComputeBudget::default(),
310 );
311 let result = process_message(
312 &message,
313 &program_indices,
314 &mut invoke_context,
315 &mut ExecuteTimings::default(),
316 &mut 0,
317 );
318 assert_eq!(
319 result,
320 Err(TransactionError::InstructionError(
321 0,
322 InstructionError::ReadonlyLamportChange
323 ))
324 );
325
326 let message = new_sanitized_message(Message::new_with_compiled_instructions(
327 1,
328 0,
329 2,
330 account_keys.clone(),
331 Hash::default(),
332 AccountKeys::new(&account_keys, None).compile_instructions(&[
333 Instruction::new_with_bincode(
334 mock_system_program_id,
335 &MockSystemInstruction::ChangeData { data: 50 },
336 account_metas,
337 ),
338 ]),
339 ));
340 let environment_config = EnvironmentConfig::new(
341 Hash::default(),
342 0,
343 0,
344 &|_| 0,
345 Arc::new(FeatureSet::all_enabled()),
346 &sysvar_cache,
347 );
348 let mut invoke_context = InvokeContext::new(
349 &mut transaction_context,
350 &mut program_cache_for_tx_batch,
351 environment_config,
352 None,
353 ComputeBudget::default(),
354 );
355 let result = process_message(
356 &message,
357 &program_indices,
358 &mut invoke_context,
359 &mut ExecuteTimings::default(),
360 &mut 0,
361 );
362 assert_eq!(
363 result,
364 Err(TransactionError::InstructionError(
365 0,
366 InstructionError::ReadonlyDataModified
367 ))
368 );
369 }
370
371 #[test]
372 fn test_process_message_duplicate_accounts() {
373 #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
374 enum MockSystemInstruction {
375 BorrowFail,
376 MultiBorrowMut,
377 DoWork { lamports: u64, data: u8 },
378 }
379
380 declare_process_instruction!(MockBuiltin, 1, |invoke_context| {
381 let transaction_context = &invoke_context.transaction_context;
382 let instruction_context = transaction_context.get_current_instruction_context()?;
383 let instruction_data = instruction_context.get_instruction_data();
384 let mut to_account =
385 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
386 if let Ok(instruction) = bincode::deserialize(instruction_data) {
387 match instruction {
388 MockSystemInstruction::BorrowFail => {
389 let from_account = instruction_context
390 .try_borrow_instruction_account(transaction_context, 0)?;
391 let dup_account = instruction_context
392 .try_borrow_instruction_account(transaction_context, 2)?;
393 if from_account.get_lamports() != dup_account.get_lamports() {
394 return Err(InstructionError::InvalidArgument);
395 }
396 Ok(())
397 }
398 MockSystemInstruction::MultiBorrowMut => {
399 let lamports_a = instruction_context
400 .try_borrow_instruction_account(transaction_context, 0)?
401 .get_lamports();
402 let lamports_b = instruction_context
403 .try_borrow_instruction_account(transaction_context, 2)?
404 .get_lamports();
405 if lamports_a != lamports_b {
406 return Err(InstructionError::InvalidArgument);
407 }
408 Ok(())
409 }
410 MockSystemInstruction::DoWork { lamports, data } => {
411 let mut dup_account = instruction_context
412 .try_borrow_instruction_account(transaction_context, 2)?;
413 dup_account.checked_sub_lamports(lamports)?;
414 to_account.checked_add_lamports(lamports)?;
415 dup_account.set_data(vec![data])?;
416 drop(dup_account);
417 let mut from_account = instruction_context
418 .try_borrow_instruction_account(transaction_context, 0)?;
419 from_account.checked_sub_lamports(lamports)?;
420 to_account.checked_add_lamports(lamports)?;
421 Ok(())
422 }
423 }
424 } else {
425 Err(InstructionError::InvalidInstructionData)
426 }
427 });
428 let mock_program_id = Pubkey::from([2u8; 32]);
429 let accounts = vec![
430 (
431 solana_pubkey::new_rand(),
432 AccountSharedData::new(100, 1, &mock_program_id),
433 ),
434 (
435 solana_pubkey::new_rand(),
436 AccountSharedData::new(0, 1, &mock_program_id),
437 ),
438 (
439 mock_program_id,
440 create_loadable_account_for_test("mock_system_program"),
441 ),
442 ];
443 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 3);
444 let program_indices = vec![vec![2]];
445 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
446 program_cache_for_tx_batch.replenish(
447 mock_program_id,
448 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
449 );
450 let account_metas = vec![
451 AccountMeta::new(
452 *transaction_context.get_key_of_account_at_index(0).unwrap(),
453 true,
454 ),
455 AccountMeta::new(
456 *transaction_context.get_key_of_account_at_index(1).unwrap(),
457 false,
458 ),
459 AccountMeta::new(
460 *transaction_context.get_key_of_account_at_index(0).unwrap(),
461 false,
462 ),
463 ];
464
465 let message = new_sanitized_message(Message::new(
467 &[Instruction::new_with_bincode(
468 mock_program_id,
469 &MockSystemInstruction::BorrowFail,
470 account_metas.clone(),
471 )],
472 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
473 ));
474 let sysvar_cache = SysvarCache::default();
475 let environment_config = EnvironmentConfig::new(
476 Hash::default(),
477 0,
478 0,
479 &|_| 0,
480 Arc::new(FeatureSet::all_enabled()),
481 &sysvar_cache,
482 );
483 let mut invoke_context = InvokeContext::new(
484 &mut transaction_context,
485 &mut program_cache_for_tx_batch,
486 environment_config,
487 None,
488 ComputeBudget::default(),
489 );
490 let result = process_message(
491 &message,
492 &program_indices,
493 &mut invoke_context,
494 &mut ExecuteTimings::default(),
495 &mut 0,
496 );
497 assert_eq!(
498 result,
499 Err(TransactionError::InstructionError(
500 0,
501 InstructionError::AccountBorrowFailed
502 ))
503 );
504
505 let message = new_sanitized_message(Message::new(
507 &[Instruction::new_with_bincode(
508 mock_program_id,
509 &MockSystemInstruction::MultiBorrowMut,
510 account_metas.clone(),
511 )],
512 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
513 ));
514 let environment_config = EnvironmentConfig::new(
515 Hash::default(),
516 0,
517 0,
518 &|_| 0,
519 Arc::new(FeatureSet::all_enabled()),
520 &sysvar_cache,
521 );
522 let mut invoke_context = InvokeContext::new(
523 &mut transaction_context,
524 &mut program_cache_for_tx_batch,
525 environment_config,
526 None,
527 ComputeBudget::default(),
528 );
529 let result = process_message(
530 &message,
531 &program_indices,
532 &mut invoke_context,
533 &mut ExecuteTimings::default(),
534 &mut 0,
535 );
536 assert!(result.is_ok());
537
538 let message = new_sanitized_message(Message::new(
540 &[Instruction::new_with_bincode(
541 mock_program_id,
542 &MockSystemInstruction::DoWork {
543 lamports: 10,
544 data: 42,
545 },
546 account_metas,
547 )],
548 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
549 ));
550 let environment_config = EnvironmentConfig::new(
551 Hash::default(),
552 0,
553 0,
554 &|_| 0,
555 Arc::new(FeatureSet::all_enabled()),
556 &sysvar_cache,
557 );
558 let mut invoke_context = InvokeContext::new(
559 &mut transaction_context,
560 &mut program_cache_for_tx_batch,
561 environment_config,
562 None,
563 ComputeBudget::default(),
564 );
565 let result = process_message(
566 &message,
567 &program_indices,
568 &mut invoke_context,
569 &mut ExecuteTimings::default(),
570 &mut 0,
571 );
572 assert!(result.is_ok());
573 assert_eq!(
574 transaction_context
575 .get_account_at_index(0)
576 .unwrap()
577 .borrow()
578 .lamports(),
579 80
580 );
581 assert_eq!(
582 transaction_context
583 .get_account_at_index(1)
584 .unwrap()
585 .borrow()
586 .lamports(),
587 20
588 );
589 assert_eq!(
590 transaction_context
591 .get_account_at_index(0)
592 .unwrap()
593 .borrow()
594 .data(),
595 &vec![42]
596 );
597 }
598
599 fn secp256k1_instruction_for_test() -> Instruction {
600 let secret_key = libsecp256k1::SecretKey::random(&mut thread_rng());
601 new_secp256k1_instruction(&secret_key, b"hello")
602 }
603
604 fn ed25519_instruction_for_test() -> Instruction {
605 let secret_key = ed25519_dalek::Keypair::generate(&mut thread_rng());
606 new_ed25519_instruction(&secret_key, b"hello")
607 }
608
609 fn secp256r1_instruction_for_test() -> Instruction {
610 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
611 let secret_key = EcKey::generate(&group).unwrap();
612 new_secp256r1_instruction(b"hello", secret_key).unwrap()
613 }
614
615 #[test]
616 fn test_precompile() {
617 let mock_program_id = Pubkey::new_unique();
618 declare_process_instruction!(MockBuiltin, 1, |_invoke_context| {
619 Err(InstructionError::Custom(0xbabb1e))
620 });
621
622 let mut secp256k1_account = AccountSharedData::new(1, 0, &native_loader::id());
623 secp256k1_account.set_executable(true);
624 let mut ed25519_account = AccountSharedData::new(1, 0, &native_loader::id());
625 ed25519_account.set_executable(true);
626 let mut secp256r1_account = AccountSharedData::new(1, 0, &native_loader::id());
627 secp256r1_account.set_executable(true);
628 let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id());
629 mock_program_account.set_executable(true);
630 let accounts = vec![
631 (
632 Pubkey::new_unique(),
633 AccountSharedData::new(1, 0, &system_program::id()),
634 ),
635 (secp256k1_program::id(), secp256k1_account),
636 (ed25519_program::id(), ed25519_account),
637 (solana_secp256r1_program::id(), secp256r1_account),
638 (mock_program_id, mock_program_account),
639 ];
640 let mut transaction_context = TransactionContext::new(accounts, Rent::default(), 1, 4);
641
642 let message = new_sanitized_message(Message::new(
643 &[
644 secp256k1_instruction_for_test(),
645 ed25519_instruction_for_test(),
646 secp256r1_instruction_for_test(),
647 Instruction::new_with_bytes(mock_program_id, &[], vec![]),
648 ],
649 Some(transaction_context.get_key_of_account_at_index(0).unwrap()),
650 ));
651 let sysvar_cache = SysvarCache::default();
652 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
653 program_cache_for_tx_batch.replenish(
654 mock_program_id,
655 Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)),
656 );
657 let environment_config = EnvironmentConfig::new(
658 Hash::default(),
659 0,
660 0,
661 &|_| 0,
662 Arc::new(FeatureSet::all_enabled()),
663 &sysvar_cache,
664 );
665 let mut invoke_context = InvokeContext::new(
666 &mut transaction_context,
667 &mut program_cache_for_tx_batch,
668 environment_config,
669 None,
670 ComputeBudget::default(),
671 );
672 let result = process_message(
673 &message,
674 &[vec![1], vec![2], vec![3], vec![4]],
675 &mut invoke_context,
676 &mut ExecuteTimings::default(),
677 &mut 0,
678 );
679
680 assert_eq!(
681 result,
682 Err(TransactionError::InstructionError(
683 3,
684 InstructionError::Custom(0xbabb1e)
685 ))
686 );
687 assert_eq!(transaction_context.get_instruction_trace_length(), 4);
688 }
689}