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::default()
35 }
36}
37
38impl MessageProcessor {
39 #[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 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 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 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 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 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}