solana_runtime/
message_processor.rs

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's fields are #[serde(skip)]-ed and not Serialize
34        // so, just rely on Default anyway.
35        MessageProcessor::default()
36    }
37}
38
39/// Resultant information gathered from calling process_message()
40#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
41pub struct ProcessedMessageInfo {
42    /// The change in accounts data len
43    pub accounts_data_len_delta: i64,
44}
45
46impl MessageProcessor {
47    /// Process a message.
48    /// This method calls each instruction in the message over the set of loaded accounts.
49    /// For each instruction it calls the program entrypoint method and verifies that the result of
50    /// the call does not violate the bank's accounting rules.
51    /// The accounts are committed back to the bank only if every instruction succeeds.
52    #[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            // Fixup the special instructions key if present
96            // before the account pre-values are taken care of
97            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        // Try to borrow mut the same account
510        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        // Try to borrow mut the same account in a safe way
545        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        // Do work on the same transaction account but at different instruction accounts
573        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}