Skip to main content

solana_transaction_context/
transaction.rs

1#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
2use {
3    crate::{
4        IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION, MAX_ACCOUNT_DATA_LEN,
5        MAX_ACCOUNTS_PER_TRANSACTION,
6        instruction::{InstructionContext, InstructionFrame},
7        transaction_accounts::{KeyedAccountSharedData, TransactionAccounts},
8        vm_addresses::{
9            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS, GUEST_INSTRUCTION_DATA_BASE_ADDRESS,
10            GUEST_REGION_SIZE, RETURN_DATA_SCRATCHPAD,
11        },
12    },
13    solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
14    solana_instruction::error::InstructionError,
15    solana_instructions_sysvar as instructions,
16    solana_rent::Rent,
17    solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
18    std::{borrow::Cow, cell::Cell, rc::Rc},
19};
20use {
21    crate::{instruction_accounts::InstructionAccount, vm_slice::VmSlice},
22    solana_pubkey::Pubkey,
23};
24
25/// Used only in fn `take_instruction_trace` for deconstructing TransactionContext
26pub type InstructionTrace<'ix_data> = (
27    Vec<InstructionFrame>,
28    Vec<Box<[InstructionAccount]>>,
29    Vec<Cow<'ix_data, [u8]>>,
30);
31
32/// This data structure is shared with programs in ABIv2, providing information about the
33/// transaction metadata.
34///
35/// Modifications without a feature gate and proper versioning might break programs.
36#[repr(C)]
37#[derive(Debug)]
38struct TransactionFrame {
39    /// Pubkey of the last program to write to the return data scratchpad
40    return_data_pubkey: Pubkey,
41    return_data_scratchpad: VmSlice<u8>,
42    /// Scratchpad for programs to write CPI instruction data
43    pub cpi_data_scratchpad: VmSlice<u8>,
44    /// Scratchpad for programs to write CPI accounts
45    pub cpi_accounts_scratchpad: VmSlice<InstructionAccount>,
46    /// Index of current executing instruction
47    current_executing_instruction: u16,
48    /// Number of instructions in the instruction trace (including top level and CPIs)
49    total_number_of_instructions_in_trace: u16,
50    /// Number of CPIs in the instruction trace
51    number_of_cpis_in_trace: u16,
52    /// Number of transaction accounts
53    number_of_transaction_accounts: u16,
54}
55
56/// Loaded transaction shared between runtime and programs.
57///
58/// This context is valid for the entire duration of a transaction being processed.
59#[derive(Debug)]
60#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
61pub struct TransactionContext<'ix_data> {
62    pub(crate) accounts: Rc<TransactionAccounts>,
63    instruction_stack_capacity: usize,
64    instruction_trace_capacity: usize,
65    instruction_stack: Vec<usize>,
66    instruction_trace: Vec<InstructionFrame>,
67    transaction_frame: TransactionFrame,
68    return_data_bytes: Vec<u8>,
69    next_top_level_instruction_index: usize,
70    #[cfg(not(target_os = "solana"))]
71    pub(crate) rent: Rent,
72    /// This is an account deduplication map that maps index_in_transaction to index_in_instruction
73    /// Usage: dedup_map[index_in_transaction] = index_in_instruction
74    /// Each entry in `deduplication_maps` represents the deduplication map for each instruction.
75    deduplication_maps: Vec<Box<[u16]>>,
76    /// Each entry in `instruction_accounts` represents the array of accounts for each instruction.
77    instruction_accounts: Vec<Box<[InstructionAccount]>>,
78    /// Each entry in `instruction_data` represents the data for instruction at the corresponding
79    /// index.
80    instruction_data: Vec<Cow<'ix_data, [u8]>>,
81}
82
83#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
84impl<'ix_data> TransactionContext<'ix_data> {
85    /// Constructs a new TransactionContext
86    pub fn new(
87        transaction_accounts: Vec<KeyedAccountSharedData>,
88        rent: Rent,
89        instruction_stack_capacity: usize,
90        instruction_trace_capacity: usize,
91        number_of_top_level_instructions: usize,
92    ) -> Self {
93        let transaction_frame = TransactionFrame {
94            return_data_pubkey: Pubkey::default(),
95            return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
96            cpi_data_scratchpad: VmSlice::new(
97                GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
98                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
99                ),
100                0,
101            ),
102            cpi_accounts_scratchpad: VmSlice::new(
103                GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
104                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
105                ),
106                0,
107            ),
108            current_executing_instruction: 0,
109            total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
110            number_of_cpis_in_trace: 0,
111            number_of_transaction_accounts: transaction_accounts.len() as u16,
112        };
113
114        // We need an extra space for the placeholder, so we avoid relocations.
115        let mut instruction_trace =
116            Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
117        instruction_trace.resize_with(
118            number_of_top_level_instructions.saturating_add(1),
119            InstructionFrame::default,
120        );
121
122        Self {
123            accounts: Rc::new(TransactionAccounts::new(transaction_accounts)),
124            instruction_stack_capacity,
125            instruction_trace_capacity,
126            instruction_stack: Vec::with_capacity(instruction_stack_capacity),
127            instruction_trace,
128            return_data_bytes: Vec::new(),
129            transaction_frame,
130            next_top_level_instruction_index: 0,
131            rent,
132            instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
133            deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
134            instruction_data: Vec::with_capacity(instruction_trace_capacity),
135        }
136    }
137
138    /// Used in mock_process_instruction
139    pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
140        if !self.instruction_stack.is_empty() {
141            return Err(InstructionError::CallDepth);
142        }
143
144        let accounts = Rc::try_unwrap(self.accounts)
145            .expect("transaction_context.accounts has unexpected outstanding refs")
146            .deconstruct_into_account_shared_data();
147
148        Ok(accounts)
149    }
150
151    pub fn accounts(&self) -> &Rc<TransactionAccounts> {
152        &self.accounts
153    }
154
155    /// Returns the total number of accounts loaded in this Transaction
156    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
157        self.accounts.len() as IndexOfAccount
158    }
159
160    /// Searches for an account by its key
161    pub fn get_key_of_account_at_index(
162        &self,
163        index_in_transaction: IndexOfAccount,
164    ) -> Result<&Pubkey, InstructionError> {
165        self.accounts
166            .account_key(index_in_transaction)
167            .ok_or(InstructionError::MissingAccount)
168    }
169
170    /// Searches for an account by its key
171    pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
172        self.accounts
173            .account_keys_iter()
174            .position(|key| key == pubkey)
175            .map(|index| index as IndexOfAccount)
176    }
177
178    /// Gets the max length of the instruction trace
179    pub fn get_instruction_trace_capacity(&self) -> usize {
180        self.instruction_trace_capacity
181    }
182
183    /// Returns the instruction trace length.
184    ///
185    /// Not counting the last empty instruction which is always pre-reserved for the next instruction.
186    pub fn get_instruction_trace_length(&self) -> usize {
187        self.instruction_trace.len().saturating_sub(1)
188    }
189
190    /// Gets a view on an instruction by its index in the trace
191    pub fn get_instruction_context_at_index_in_trace(
192        &self,
193        index_in_trace: usize,
194    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
195        let instruction = self
196            .instruction_trace
197            .get(index_in_trace)
198            .ok_or(InstructionError::CallDepth)?;
199
200        // These commands will return a default empty slice if we are retrieving an instruction
201        // that hasn't been configured yet.
202        let instruction_accounts = self
203            .instruction_accounts
204            .get(index_in_trace)
205            .map(|item| item.as_ref())
206            .unwrap_or_default();
207        let dedup_map = self
208            .deduplication_maps
209            .get(index_in_trace)
210            .map(|item| item.as_ref())
211            .unwrap_or_default();
212        let instruction_data = self
213            .instruction_data
214            .get(index_in_trace)
215            .map(|item| item.as_ref())
216            .unwrap_or_default();
217        Ok(InstructionContext {
218            transaction_context: self,
219            index_in_trace,
220            nesting_level: instruction.nesting_level as usize,
221            program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
222            instruction_accounts,
223            dedup_map,
224            instruction_data,
225            index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
226        })
227    }
228
229    /// Gets a view on the instruction by its nesting level in the stack
230    pub fn get_instruction_context_at_nesting_level(
231        &self,
232        nesting_level: usize,
233    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
234        let index_in_trace = *self
235            .instruction_stack
236            .get(nesting_level)
237            .ok_or(InstructionError::CallDepth)?;
238        let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
239        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
240        Ok(instruction_context)
241    }
242
243    /// Gets the max height of the instruction stack
244    pub fn get_instruction_stack_capacity(&self) -> usize {
245        self.instruction_stack_capacity
246    }
247
248    /// Gets instruction stack height, top-level instructions are height
249    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
250    pub fn get_instruction_stack_height(&self) -> usize {
251        self.instruction_stack.len()
252    }
253
254    /// Returns the index in the instruction trace of the current executing instruction
255    pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
256        self.instruction_stack
257            .last()
258            .copied()
259            .ok_or(InstructionError::CallDepth)
260    }
261
262    /// Returns a view on the current instruction
263    pub fn get_current_instruction_context(
264        &self,
265    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
266        let index_in_trace = self.get_current_instruction_index()?;
267        self.get_instruction_context_at_index_in_trace(index_in_trace)
268    }
269
270    /// Returns a view on the next instruction. This function assumes it has already been
271    /// configured with the correct values in `prepare_next_instruction` or
272    /// `prepare_next_top_level_instruction`
273    pub fn get_next_instruction_context(
274        &self,
275    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
276        let index_in_trace = if self.instruction_stack.is_empty() {
277            self.next_top_level_instruction_index
278        } else {
279            self.instruction_trace
280                .len()
281                .checked_sub(1)
282                .ok_or(InstructionError::CallDepth)?
283        };
284        self.get_instruction_context_at_index_in_trace(index_in_trace)
285    }
286
287    /// Configures an instruction at a specific index in trace.
288    pub fn configure_instruction_at_index(
289        &mut self,
290        instruction_index: usize,
291        program_index: IndexOfAccount,
292        instruction_accounts: Vec<InstructionAccount>,
293        deduplication_map: Vec<u16>,
294        instruction_data: Cow<'ix_data, [u8]>,
295        caller_index: Option<u16>,
296    ) -> Result<(), InstructionError> {
297        debug_assert_eq!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION);
298
299        let instruction = self
300            .instruction_trace
301            .get_mut(instruction_index)
302            .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
303
304        // If we have a parent index, then we are dealing with a CPI.
305        if let Some(caller_index) = caller_index {
306            self.transaction_frame.total_number_of_instructions_in_trace = self
307                .transaction_frame
308                .total_number_of_instructions_in_trace
309                .saturating_add(1);
310            instruction.index_of_caller_instruction = caller_index;
311            let next_data_ptr = self
312                .transaction_frame
313                .cpi_data_scratchpad
314                .ptr()
315                .saturating_add(GUEST_REGION_SIZE);
316            self.transaction_frame.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
317            let next_accounts_ptr = self
318                .transaction_frame
319                .cpi_accounts_scratchpad
320                .ptr()
321                .saturating_add(GUEST_REGION_SIZE);
322            self.transaction_frame.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
323        }
324
325        instruction.program_account_index_in_tx = program_index;
326        instruction.configure_vm_slices(
327            instruction_index as u64,
328            instruction_accounts.len(),
329            instruction_data.len() as u64,
330        );
331        self.deduplication_maps
332            .push(deduplication_map.into_boxed_slice());
333        self.instruction_accounts
334            .push(instruction_accounts.into_boxed_slice());
335        self.instruction_data.push(instruction_data);
336        Ok(())
337    }
338
339    /// For tests only
340    fn deduplicate_accounts_for_tests(instruction_accounts: &[InstructionAccount]) -> Vec<u16> {
341        let mut dedup_map = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
342        for (idx, account) in instruction_accounts.iter().enumerate() {
343            let index_in_instruction = dedup_map
344                .get_mut(account.index_in_transaction as usize)
345                .unwrap();
346            if *index_in_instruction == u16::MAX {
347                *index_in_instruction = idx as u16;
348            }
349        }
350        dedup_map
351    }
352
353    /// A version of `configure_top_level_instruction` to help creating the deduplication map in tests
354    pub fn configure_top_level_instruction_for_tests(
355        &mut self,
356        program_index: IndexOfAccount,
357        instruction_accounts: Vec<InstructionAccount>,
358        instruction_data: Vec<u8>,
359    ) -> Result<(), InstructionError> {
360        debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
361        let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
362
363        self.configure_instruction_at_index(
364            self.next_top_level_instruction_index,
365            program_index,
366            instruction_accounts,
367            dedup_map,
368            Cow::Owned(instruction_data),
369            None,
370        )?;
371        Ok(())
372    }
373
374    /// A helper function to facilitate creating a CPI in tests
375    pub fn configure_next_cpi_for_tests(
376        &mut self,
377        program_index: IndexOfAccount,
378        instruction_accounts: Vec<InstructionAccount>,
379        instruction_data: Vec<u8>,
380    ) -> Result<(), InstructionError> {
381        debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
382        let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
383        let caller_index = self.get_current_instruction_index()?;
384        let cpi_index = self.get_instruction_trace_length();
385        self.configure_instruction_at_index(
386            cpi_index,
387            program_index,
388            instruction_accounts,
389            dedup_map,
390            Cow::Owned(instruction_data),
391            Some(caller_index as u16),
392        )?;
393        Ok(())
394    }
395
396    /// Pushes the next instruction
397    pub fn push(&mut self) -> Result<(), InstructionError> {
398        let nesting_level = self.get_instruction_stack_height();
399        if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
400            return Err(InstructionError::UnbalancedInstruction);
401        }
402        {
403            let instruction = self
404                .instruction_trace
405                .last_mut()
406                .ok_or(InstructionError::CallDepth)?;
407            instruction.nesting_level = nesting_level as u16;
408        }
409
410        if self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
411            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
412        }
413
414        let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
415            let index = self.next_top_level_instruction_index;
416            self.next_top_level_instruction_index =
417                self.next_top_level_instruction_index.saturating_add(1);
418            (index, index)
419        } else {
420            let index = self.get_instruction_trace_length();
421            self.transaction_frame.number_of_cpis_in_trace = self
422                .transaction_frame
423                .number_of_cpis_in_trace
424                .saturating_add(1);
425            self.instruction_trace.push(InstructionFrame::default());
426            (
427                index,
428                self.next_top_level_instruction_index.saturating_sub(1),
429            )
430        };
431
432        if nesting_level >= self.instruction_stack_capacity {
433            return Err(InstructionError::CallDepth);
434        }
435        self.transaction_frame.current_executing_instruction = index_in_trace as u16;
436        self.instruction_stack.push(index_in_trace);
437        if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
438            let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
439            if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
440                return Err(InstructionError::InvalidAccountOwner);
441            }
442            instructions::store_current_index_checked(
443                mut_account_ref.data_as_mut_slice(),
444                current_top_level_instruction as u16,
445            )?;
446        }
447        Ok(())
448    }
449
450    /// Pops the current instruction
451    pub fn pop(&mut self) -> Result<(), InstructionError> {
452        if self.instruction_stack.is_empty() {
453            return Err(InstructionError::CallDepth);
454        }
455        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
456        let detected_an_unbalanced_instruction =
457            self.get_current_instruction_context()
458                .and_then(|instruction_context| {
459                    // Verify all executable accounts have no outstanding refs
460                    self.accounts
461                        .try_borrow_mut(
462                            instruction_context.get_index_of_program_account_in_transaction()?,
463                        )
464                        .map_err(|err| {
465                            if err == InstructionError::AccountBorrowFailed {
466                                InstructionError::AccountBorrowOutstanding
467                            } else {
468                                err
469                            }
470                        })?;
471                    Ok(self.accounts.get_lamports_delta() != 0)
472                });
473        // Always pop, even if we `detected_an_unbalanced_instruction`
474        self.instruction_stack.pop();
475        if let Some(instr_idx) = self.instruction_stack.last() {
476            self.transaction_frame.current_executing_instruction = *instr_idx as u16;
477        }
478        if detected_an_unbalanced_instruction? {
479            Err(InstructionError::UnbalancedInstruction)
480        } else {
481            Ok(())
482        }
483    }
484
485    /// Gets the return data of the current instruction or any above
486    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
487        (
488            &self.transaction_frame.return_data_pubkey,
489            &self.return_data_bytes,
490        )
491    }
492
493    /// Set the return data of the current instruction
494    pub fn set_return_data(
495        &mut self,
496        program_id: Pubkey,
497        data: Vec<u8>,
498    ) -> Result<(), InstructionError> {
499        self.transaction_frame.return_data_pubkey = program_id;
500        // SAFETY: `return_data_scratchpad` is backed by `self.return_data_bytes`
501        // and `return_data_bytes` is being reset to `data`
502        // in the next statement.
503        unsafe {
504            self.transaction_frame
505                .return_data_scratchpad
506                .set_len(data.len() as u64);
507        }
508        self.return_data_bytes = data;
509        Ok(())
510    }
511
512    /// Returns a new account data write access handler
513    pub fn access_violation_handler(
514        &self,
515        virtual_address_space_adjustments: bool,
516        account_data_direct_mapping: bool,
517    ) -> AccessViolationHandler {
518        let accounts = Rc::clone(&self.accounts);
519        Box::new(
520            move |region: &mut MemoryRegion,
521                  address_space_reserved_for_account: u64,
522                  access_type: AccessType,
523                  vm_addr: u64,
524                  len: u64| {
525                if access_type == AccessType::Load {
526                    return;
527                }
528                let Some(index_in_transaction) = region.access_violation_handler_payload else {
529                    // This region is not a writable account.
530                    return;
531                };
532                let requested_length =
533                    vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
534                if requested_length > address_space_reserved_for_account as usize {
535                    // Requested access goes further than the account region.
536                    return;
537                }
538
539                // The four calls below can't really fail. If they fail because of a bug,
540                // whatever is writing will trigger an EbpfError::AccessViolation like
541                // if the region was readonly, and the transaction will fail gracefully.
542                let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
543                    debug_assert!(false);
544                    return;
545                };
546                if accounts.touch(index_in_transaction).is_err() {
547                    debug_assert!(false);
548                    return;
549                }
550
551                let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
552                    .saturating_sub(accounts.resize_delta())
553                    .max(0) as usize;
554
555                if requested_length > region.len as usize {
556                    // Realloc immediately here to fit the requested access,
557                    // then later in CPI or deserialization realloc again to the
558                    // account length the program stored in AccountInfo.
559                    let old_len = account.data().len();
560                    let new_len = (address_space_reserved_for_account as usize)
561                        .min(MAX_ACCOUNT_DATA_LEN as usize)
562                        .min(old_len.saturating_add(remaining_allowed_growth));
563                    // The last two min operations ensure the following:
564                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
565                    if accounts
566                        .update_accounts_resize_delta(old_len, new_len)
567                        .is_err()
568                    {
569                        return;
570                    }
571                    account.resize(new_len, 0);
572                    region.len = new_len as u64;
573                }
574
575                // Potentially unshare / make the account shared data unique (CoW logic).
576                if virtual_address_space_adjustments && account_data_direct_mapping {
577                    region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
578                    region.writable = true;
579                }
580            },
581        )
582    }
583
584    /// Take ownership of the instruction trace
585    pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
586        // The last frame is a placeholder for the next instruction to be executed, so it
587        // is empty.
588        self.instruction_trace.pop();
589        (
590            std::mem::take(&mut self.instruction_trace),
591            std::mem::take(&mut self.instruction_accounts),
592            std::mem::take(&mut self.instruction_data),
593        )
594    }
595
596    /// Called instruction are those that the program runtime has already called into. It
597    /// encompasses instructions under execution (e.g. all nested CPIs are already called) and
598    /// finished ones.
599    ///
600    /// Top level instructions that have not yet been executed aren't considered called.
601    pub fn number_of_called_instructions_in_trace(&self) -> usize {
602        self.next_top_level_instruction_index
603            .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
604    }
605
606    /// Return next top level instruction to execute
607    pub fn next_top_level_instruction_index(&self) -> usize {
608        self.next_top_level_instruction_index
609    }
610
611    /// Return number of CPIs in instruction trace
612    pub fn number_of_cpis_in_trace(&self) -> usize {
613        self.transaction_frame.number_of_cpis_in_trace as usize
614    }
615}
616
617/// Return data at the end of a transaction
618#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
619#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
620#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
621#[derive(Clone, Debug, Default, PartialEq, Eq)]
622pub struct TransactionReturnData {
623    pub program_id: Pubkey,
624    pub data: Vec<u8>,
625}
626
627/// Everything that needs to be recorded from a TransactionContext after execution
628#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
629pub struct ExecutionRecord {
630    pub accounts: Vec<KeyedAccountSharedData>,
631    pub return_data: TransactionReturnData,
632    /// Parallel to `accounts`: whether each account was modified by the VM.
633    pub touched_flags: Box<[bool]>,
634    pub accounts_resize_delta: i64,
635}
636
637/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
638#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
639impl From<TransactionContext<'_>> for ExecutionRecord {
640    fn from(context: TransactionContext) -> Self {
641        let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
642            .expect("transaction_context.accounts has unexpected outstanding refs")
643            .take();
644
645        // The flags only needed interior mutability while the VM was running.
646        // Now that we own them, unwrap the per-element `Cell`s into a plain
647        // `Box<[bool]>`. `Vec::from` reuses the box's allocation and the mapped
648        // collect reuses that same buffer in place (`Cell<bool>` and `bool` have
649        // identical layout), so no reallocation occurs.
650        let touched_flags: Box<[bool]> = Vec::from(touched_flags)
651            .into_iter()
652            .map(|flag| flag.into_inner())
653            .collect();
654
655        let return_data = TransactionReturnData {
656            program_id: context.transaction_frame.return_data_pubkey,
657            data: context.return_data_bytes,
658        };
659
660        Self {
661            accounts,
662            return_data,
663            touched_flags,
664            accounts_resize_delta: Cell::into_inner(resize_delta),
665        }
666    }
667}
668
669#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
670mod tests {
671    use super::*;
672
673    #[test]
674    fn test_instructions_sysvar_store_index_checked() {
675        let build_transaction_context = |account: AccountSharedData| {
676            TransactionContext::new(
677                vec![
678                    (Pubkey::new_unique(), AccountSharedData::default()),
679                    (instructions::id(), account),
680                ],
681                Rent::default(),
682                /* max_instruction_stack_depth */ 2,
683                /* max_instruction_trace_length */ 2,
684                /* number_of_top_level_instructions */ 1,
685            )
686        };
687
688        let correct_space = 2;
689        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
690
691        // First try it with the wrong owner.
692        let account =
693            AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
694        assert_eq!(
695            build_transaction_context(account).push(),
696            Err(InstructionError::InvalidAccountOwner),
697        );
698
699        // Now with the wrong data length.
700        let account =
701            AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
702        assert_eq!(
703            build_transaction_context(account).push(),
704            Err(InstructionError::AccountDataTooSmall),
705        );
706
707        // Finally provide the correct account setup.
708        let account = AccountSharedData::new(
709            rent_exempt_lamports,
710            correct_space,
711            &solana_sdk_ids::sysvar::id(),
712        );
713        assert_eq!(build_transaction_context(account).push(), Ok(()),);
714    }
715
716    #[test]
717    fn test_invalid_native_loader_index() {
718        let mut transaction_context = TransactionContext::new(
719            vec![(
720                Pubkey::new_unique(),
721                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
722            )],
723            Rent::default(),
724            20,
725            20,
726            1,
727        );
728
729        transaction_context
730            .configure_top_level_instruction_for_tests(
731                u16::MAX,
732                vec![InstructionAccount::new(0, false, false)],
733                vec![],
734            )
735            .unwrap();
736        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
737
738        let result = instruction_context.get_index_of_program_account_in_transaction();
739        assert_eq!(result, Err(InstructionError::MissingAccount));
740
741        let result = instruction_context.get_program_key();
742        assert_eq!(result, Err(InstructionError::MissingAccount));
743
744        let result = instruction_context.get_program_owner();
745        assert_eq!(result.err(), Some(InstructionError::MissingAccount));
746    }
747
748    #[test]
749    fn test_instruction_shared_items() {
750        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 10];
751        let mut transaction_context =
752            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
753
754        let instruction_accounts_1 = vec![
755            InstructionAccount::new(0, false, true),
756            InstructionAccount::new(3, true, false),
757        ];
758        transaction_context
759            .configure_top_level_instruction_for_tests(
760                1,
761                instruction_accounts_1.clone(),
762                vec![1, 2, 3, 4],
763            )
764            .unwrap();
765        transaction_context.push().unwrap();
766
767        let instruction_accounts_2 = vec![
768            InstructionAccount::new(0, false, true),
769            InstructionAccount::new(3, true, false),
770            InstructionAccount::new(5, false, false),
771        ];
772        transaction_context
773            .configure_top_level_instruction_for_tests(
774                1,
775                instruction_accounts_2.clone(),
776                vec![5, 6, 7, 8, 9],
777            )
778            .unwrap();
779        transaction_context.push().unwrap();
780
781        let instruction_accounts_3 = vec![
782            InstructionAccount::new(0, false, true),
783            InstructionAccount::new(3, true, false),
784            InstructionAccount::new(5, false, false),
785            InstructionAccount::new(3, false, false),
786            InstructionAccount::new(10, false, false),
787        ];
788        transaction_context
789            .configure_top_level_instruction_for_tests(
790                1,
791                instruction_accounts_3.clone(),
792                vec![10, 11],
793            )
794            .unwrap();
795        transaction_context.push().unwrap();
796
797        let first_ix_context = transaction_context
798            .get_instruction_context_at_index_in_trace(0)
799            .unwrap();
800        assert_eq!(
801            instruction_accounts_1.as_slice(),
802            first_ix_context.instruction_accounts
803        );
804        assert_eq!(
805            *first_ix_context.instruction_data,
806            **transaction_context.instruction_data.first().unwrap()
807        );
808        for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
809            assert_eq!(
810                *first_ix_context
811                    .dedup_map
812                    .get(acc.index_in_transaction as usize)
813                    .unwrap(),
814                idx_in_ix as u16
815            );
816        }
817
818        let second_ix_context = transaction_context
819            .get_instruction_context_at_index_in_trace(1)
820            .unwrap();
821        assert_eq!(
822            instruction_accounts_2.as_slice(),
823            second_ix_context.instruction_accounts
824        );
825        assert_eq!(
826            *second_ix_context.instruction_data,
827            **transaction_context.instruction_data.get(1).unwrap()
828        );
829        for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
830            assert_eq!(
831                *second_ix_context
832                    .dedup_map
833                    .get(acc.index_in_transaction as usize)
834                    .unwrap(),
835                idx_in_ix as u16
836            );
837        }
838
839        let third_ix_context = transaction_context
840            .get_instruction_context_at_index_in_trace(2)
841            .unwrap();
842        assert_eq!(
843            instruction_accounts_3.as_slice(),
844            third_ix_context.instruction_accounts
845        );
846        assert_eq!(
847            *third_ix_context.instruction_data,
848            **transaction_context.instruction_data.get(2).unwrap()
849        );
850        for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
851            if idx_in_ix == 3 {
852                assert_eq!(
853                    *third_ix_context
854                        .dedup_map
855                        .get(acc.index_in_transaction as usize)
856                        .unwrap(),
857                    1
858                );
859            } else {
860                assert_eq!(
861                    *third_ix_context
862                        .dedup_map
863                        .get(acc.index_in_transaction as usize)
864                        .unwrap(),
865                    idx_in_ix as u16
866                );
867            }
868        }
869    }
870
871    #[test]
872    fn test_number_of_instructions() {
873        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
874        let mut transaction_context =
875            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
876        assert_eq!(
877            transaction_context
878                .transaction_frame
879                .number_of_cpis_in_trace,
880            0
881        );
882
883        // Instruction #0
884        transaction_context
885            .configure_instruction_at_index(
886                0,
887                0,
888                vec![InstructionAccount::new(1, false, false)],
889                vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
890                Vec::new().into(),
891                None,
892            )
893            .unwrap();
894
895        // Instruction #1
896        transaction_context
897            .configure_instruction_at_index(
898                1,
899                0,
900                vec![InstructionAccount::new(1, false, false)],
901                vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
902                Vec::new().into(),
903                None,
904            )
905            .unwrap();
906
907        // Executing instruction #0
908        transaction_context.push().unwrap();
909        assert_eq!(
910            transaction_context
911                .transaction_frame
912                .current_executing_instruction,
913            0
914        );
915        assert_eq!(
916            transaction_context.number_of_called_instructions_in_trace(),
917            1
918        );
919
920        assert_eq!(
921            transaction_context
922                .transaction_frame
923                .total_number_of_instructions_in_trace,
924            2
925        );
926
927        assert_eq!(
928            transaction_context
929                .transaction_frame
930                .number_of_cpis_in_trace,
931            0
932        );
933
934        assert_eq!(
935            transaction_context
936                .transaction_frame
937                .cpi_data_scratchpad
938                .ptr(),
939            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
940        );
941        assert_eq!(
942            transaction_context
943                .transaction_frame
944                .cpi_data_scratchpad
945                .len(),
946            0,
947        );
948        assert_eq!(
949            transaction_context
950                .transaction_frame
951                .cpi_accounts_scratchpad
952                .ptr(),
953            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
954                .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
955        );
956        assert_eq!(
957            transaction_context
958                .transaction_frame
959                .cpi_data_scratchpad
960                .len(),
961            0,
962        );
963
964        assert_eq!(
965            transaction_context.number_of_called_instructions_in_trace(),
966            1
967        );
968
969        // Instruction #0 does a CPI.
970        transaction_context
971            .configure_next_cpi_for_tests(
972                0,
973                vec![InstructionAccount::new(2, false, true)],
974                Vec::new(),
975            )
976            .unwrap();
977
978        transaction_context.push().unwrap();
979        assert_eq!(
980            transaction_context
981                .transaction_frame
982                .current_executing_instruction,
983            2
984        );
985
986        assert_eq!(
987            transaction_context
988                .transaction_frame
989                .total_number_of_instructions_in_trace,
990            3
991        );
992        assert_eq!(
993            transaction_context
994                .transaction_frame
995                .number_of_cpis_in_trace,
996            1
997        );
998        assert_eq!(
999            transaction_context.number_of_called_instructions_in_trace(),
1000            2
1001        );
1002
1003        assert_eq!(
1004            transaction_context
1005                .transaction_frame
1006                .cpi_data_scratchpad
1007                .ptr(),
1008            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1009        );
1010        assert_eq!(
1011            transaction_context
1012                .transaction_frame
1013                .cpi_accounts_scratchpad
1014                .ptr(),
1015            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1016                .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1017        );
1018
1019        // A nested CPI
1020        transaction_context
1021            .configure_next_cpi_for_tests(
1022                0,
1023                vec![InstructionAccount::new(2, false, true)],
1024                Vec::new(),
1025            )
1026            .unwrap();
1027
1028        transaction_context.push().unwrap();
1029        assert_eq!(
1030            transaction_context
1031                .transaction_frame
1032                .current_executing_instruction,
1033            3
1034        );
1035
1036        assert_eq!(
1037            transaction_context
1038                .transaction_frame
1039                .total_number_of_instructions_in_trace,
1040            4
1041        );
1042
1043        assert_eq!(
1044            transaction_context
1045                .transaction_frame
1046                .cpi_data_scratchpad
1047                .ptr(),
1048            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1049        );
1050        assert_eq!(
1051            transaction_context
1052                .transaction_frame
1053                .cpi_accounts_scratchpad
1054                .ptr(),
1055            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1056                .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1057        );
1058
1059        assert_eq!(
1060            transaction_context
1061                .transaction_frame
1062                .number_of_cpis_in_trace,
1063            2
1064        );
1065
1066        assert_eq!(
1067            transaction_context.number_of_called_instructions_in_trace(),
1068            3
1069        );
1070        // Return from nested CPI
1071        transaction_context.pop().unwrap();
1072        assert_eq!(
1073            transaction_context.number_of_called_instructions_in_trace(),
1074            3
1075        );
1076
1077        assert_eq!(
1078            transaction_context
1079                .transaction_frame
1080                .total_number_of_instructions_in_trace,
1081            4
1082        );
1083        assert_eq!(
1084            transaction_context
1085                .transaction_frame
1086                .number_of_cpis_in_trace,
1087            2,
1088        );
1089        assert_eq!(
1090            transaction_context
1091                .transaction_frame
1092                .current_executing_instruction,
1093            2
1094        );
1095
1096        // A second nested CPI
1097        transaction_context
1098            .configure_next_cpi_for_tests(
1099                0,
1100                vec![InstructionAccount::new(2, false, true)],
1101                Vec::new(),
1102            )
1103            .unwrap();
1104
1105        transaction_context.push().unwrap();
1106        assert_eq!(
1107            transaction_context
1108                .transaction_frame
1109                .current_executing_instruction,
1110            4
1111        );
1112
1113        assert_eq!(
1114            transaction_context
1115                .transaction_frame
1116                .total_number_of_instructions_in_trace,
1117            5
1118        );
1119
1120        assert_eq!(
1121            transaction_context
1122                .transaction_frame
1123                .cpi_data_scratchpad
1124                .ptr(),
1125            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1126        );
1127        assert_eq!(
1128            transaction_context
1129                .transaction_frame
1130                .cpi_accounts_scratchpad
1131                .ptr(),
1132            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1133                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1134        );
1135
1136        assert_eq!(
1137            transaction_context
1138                .transaction_frame
1139                .number_of_cpis_in_trace,
1140            3
1141        );
1142        assert_eq!(
1143            transaction_context.number_of_called_instructions_in_trace(),
1144            4
1145        );
1146
1147        // Return from second nested CPI
1148        transaction_context.pop().unwrap();
1149
1150        assert_eq!(
1151            transaction_context
1152                .transaction_frame
1153                .current_executing_instruction,
1154            2
1155        );
1156
1157        assert_eq!(
1158            transaction_context
1159                .transaction_frame
1160                .total_number_of_instructions_in_trace,
1161            5
1162        );
1163
1164        assert_eq!(
1165            transaction_context
1166                .transaction_frame
1167                .cpi_data_scratchpad
1168                .ptr(),
1169            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1170        );
1171        assert_eq!(
1172            transaction_context
1173                .transaction_frame
1174                .cpi_accounts_scratchpad
1175                .ptr(),
1176            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1177                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1178        );
1179
1180        assert_eq!(
1181            transaction_context
1182                .transaction_frame
1183                .number_of_cpis_in_trace,
1184            3
1185        );
1186
1187        // Return from first CPI
1188        transaction_context.pop().unwrap();
1189        assert_eq!(
1190            transaction_context.number_of_called_instructions_in_trace(),
1191            4
1192        );
1193
1194        assert_eq!(
1195            transaction_context
1196                .transaction_frame
1197                .current_executing_instruction,
1198            0
1199        );
1200
1201        assert_eq!(
1202            transaction_context
1203                .transaction_frame
1204                .total_number_of_instructions_in_trace,
1205            5
1206        );
1207
1208        assert_eq!(
1209            transaction_context
1210                .transaction_frame
1211                .cpi_data_scratchpad
1212                .ptr(),
1213            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1214        );
1215        assert_eq!(
1216            transaction_context
1217                .transaction_frame
1218                .cpi_accounts_scratchpad
1219                .ptr(),
1220            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1221                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1222        );
1223
1224        assert_eq!(
1225            transaction_context
1226                .transaction_frame
1227                .number_of_cpis_in_trace,
1228            3,
1229        );
1230
1231        // Let's go to Instruction #1 (top level)
1232        transaction_context.pop().unwrap();
1233        transaction_context.push().unwrap();
1234        assert_eq!(
1235            transaction_context
1236                .transaction_frame
1237                .current_executing_instruction,
1238            1,
1239        );
1240        assert_eq!(
1241            transaction_context
1242                .transaction_frame
1243                .number_of_cpis_in_trace,
1244            3
1245        );
1246
1247        // Instruction #1 will do a CPI.
1248        transaction_context
1249            .configure_next_cpi_for_tests(
1250                0,
1251                vec![InstructionAccount::new(2, false, true)],
1252                Vec::new(),
1253            )
1254            .unwrap();
1255
1256        transaction_context.push().unwrap();
1257
1258        assert_eq!(
1259            transaction_context
1260                .transaction_frame
1261                .current_executing_instruction,
1262            5,
1263        );
1264
1265        assert_eq!(
1266            transaction_context
1267                .transaction_frame
1268                .total_number_of_instructions_in_trace,
1269            6
1270        );
1271
1272        assert_eq!(
1273            transaction_context
1274                .transaction_frame
1275                .cpi_data_scratchpad
1276                .ptr(),
1277            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1278        );
1279        assert_eq!(
1280            transaction_context
1281                .transaction_frame
1282                .cpi_accounts_scratchpad
1283                .ptr(),
1284            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1285                .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1286        );
1287
1288        assert_eq!(
1289            transaction_context
1290                .transaction_frame
1291                .number_of_cpis_in_trace,
1292            4
1293        );
1294        assert_eq!(
1295            transaction_context.number_of_called_instructions_in_trace(),
1296            6
1297        );
1298
1299        // Return from CPI
1300        transaction_context.pop().unwrap();
1301        assert_eq!(
1302            transaction_context
1303                .transaction_frame
1304                .number_of_cpis_in_trace,
1305            4
1306        );
1307        assert_eq!(
1308            transaction_context
1309                .transaction_frame
1310                .current_executing_instruction,
1311            1,
1312        );
1313
1314        transaction_context.pop().unwrap();
1315    }
1316
1317    #[test]
1318    fn test_get_current_instruction_index() {
1319        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1320        let mut transaction_context =
1321            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1322
1323        // First top level instruction
1324        transaction_context
1325            .configure_instruction_at_index(
1326                0,
1327                1,
1328                vec![
1329                    InstructionAccount::new(0, false, false),
1330                    InstructionAccount::new(1, false, false),
1331                ],
1332                vec![u16::MAX; 256],
1333                Cow::Owned(Vec::new()),
1334                None,
1335            )
1336            .unwrap();
1337
1338        // Second top-level instruction
1339        transaction_context
1340            .configure_instruction_at_index(
1341                1,
1342                1,
1343                vec![
1344                    InstructionAccount::new(0, false, false),
1345                    InstructionAccount::new(1, false, true),
1346                ],
1347                vec![u16::MAX; 256],
1348                Cow::Owned(Vec::new()),
1349                None,
1350            )
1351            .unwrap();
1352
1353        transaction_context.push().unwrap();
1354        assert_eq!(
1355            transaction_context.get_current_instruction_index().unwrap(),
1356            0
1357        );
1358
1359        transaction_context.pop().unwrap();
1360
1361        transaction_context.push().unwrap();
1362        assert_eq!(
1363            transaction_context.get_current_instruction_index().unwrap(),
1364            1
1365        );
1366
1367        // Simulating a CPI
1368        transaction_context
1369            .configure_next_cpi_for_tests(
1370                1,
1371                vec![
1372                    InstructionAccount::new(0, false, true),
1373                    InstructionAccount::new(1, false, false),
1374                ],
1375                Vec::new(),
1376            )
1377            .unwrap();
1378        transaction_context.push().unwrap();
1379        assert_eq!(
1380            transaction_context.get_current_instruction_index().unwrap(),
1381            2
1382        );
1383
1384        // Yet another CPI
1385        transaction_context
1386            .configure_next_cpi_for_tests(
1387                1,
1388                vec![
1389                    InstructionAccount::new(0, false, true),
1390                    InstructionAccount::new(1, false, false),
1391                ],
1392                Vec::new(),
1393            )
1394            .unwrap();
1395        transaction_context.push().unwrap();
1396        assert_eq!(
1397            transaction_context.get_current_instruction_index().unwrap(),
1398            3
1399        );
1400
1401        // CPI return
1402        transaction_context.pop().unwrap();
1403        assert_eq!(
1404            transaction_context.get_current_instruction_index().unwrap(),
1405            2
1406        );
1407
1408        // CPI return 2
1409        transaction_context.pop().unwrap();
1410        assert_eq!(
1411            transaction_context.get_current_instruction_index().unwrap(),
1412            1
1413        );
1414    }
1415}