solana_svm/
message_processor.rs

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