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
11pub(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 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 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 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}