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