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