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