clone_solana_svm/
message_processor.rs

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