solana_svm/
message_processor.rs

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