solana_svm/
message_processor.rs

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