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