Skip to main content

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