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