solana_runtime/
message_processor.rs

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'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<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            // Fixup the special instructions key if present
94            // before the account pre-values are taken care of
95            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        // Try to borrow mut the same account
525        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        // Try to borrow mut the same account in a safe way
560        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        // Do work on the same transaction account but at different instruction accounts
588        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}