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