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