miraland_program_runtime/
message_processor.rs

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