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        self.transaction_frame
501            .return_data_scratchpad
502            .set_len(data.len() as u64);
503        self.return_data_bytes = data;
504        Ok(())
505    }
506
507    /// Returns a new account data write access handler
508    pub fn access_violation_handler(
509        &self,
510        virtual_address_space_adjustments: bool,
511        account_data_direct_mapping: bool,
512    ) -> AccessViolationHandler {
513        let accounts = Rc::clone(&self.accounts);
514        Box::new(
515            move |region: &mut MemoryRegion,
516                  address_space_reserved_for_account: u64,
517                  access_type: AccessType,
518                  vm_addr: u64,
519                  len: u64| {
520                if access_type == AccessType::Load {
521                    return;
522                }
523                let Some(index_in_transaction) = region.access_violation_handler_payload else {
524                    // This region is not a writable account.
525                    return;
526                };
527                let region_vm_addr_start = region.vm_addr_range().start;
528                let requested_length = vm_addr
529                    .saturating_add(len)
530                    .saturating_sub(region_vm_addr_start)
531                    as usize;
532                if requested_length > address_space_reserved_for_account as usize {
533                    // Requested access goes further than the account region.
534                    return;
535                }
536
537                // The four calls below can't really fail. If they fail because of a bug,
538                // whatever is writing will trigger an EbpfError::AccessViolation like
539                // if the region was readonly, and the transaction will fail gracefully.
540                let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
541                    debug_assert!(false);
542                    return;
543                };
544                if accounts.touch(index_in_transaction).is_err() {
545                    debug_assert!(false);
546                    return;
547                }
548
549                let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
550                    .saturating_sub(accounts.resize_delta())
551                    .max(0) as usize;
552
553                if requested_length > region.len() {
554                    // Realloc immediately here to fit the requested access,
555                    // then later in CPI or deserialization realloc again to the
556                    // account length the program stored in AccountInfo.
557                    let old_len = account.data().len();
558                    let new_len = (address_space_reserved_for_account as usize)
559                        .min(MAX_ACCOUNT_DATA_LEN as usize)
560                        .min(old_len.saturating_add(remaining_allowed_growth));
561                    // The last two min operations ensure the following:
562                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
563                    if accounts
564                        .update_accounts_resize_delta(old_len, new_len)
565                        .is_err()
566                    {
567                        return;
568                    }
569                    unsafe {
570                        account.resize(new_len, 0);
571                        // SAFETY:
572                        //
573                        // Contract from `MemoryRegion::redirect`: MemoryRegion must point to a
574                        // valid object live for the duration of this `MemoryMapping`.
575                        //
576                        // Evidence: There are two distinct cases, when the account buffer is
577                        // serialized and when the account buffer is directly mapped.
578                        // * In the serialization case we continue pointing at the same buffer as
579                        // before, and the original buffer must have satisfied the liveness
580                        // condition before.
581                        // * In the direct mapping case `account.resize` invalidates the buffer this
582                        // region has been pointing at, but this is fixed up later in the "unshare"
583                        // branch later.
584                        //
585                        // Contract from `MemoryRegion::redirect`: For `MemoryRegion`s marked
586                        // writable, the host buffer must accept arbitrary bytes being overwritten
587                        // without it resulting in unsoundness.
588                        //
589                        // Evidence: The account payloads dont have any internal soundness
590                        // invariants. The buffer in the serialization case starts off and remains
591                        // writable (even though the HostBuffer might have been initially created as
592                        // immutable.) In the direct mapping case we redirect the region to the
593                        // buffer stored in the account later on.
594                        //
595                        // Contract from `HostBuffer::mutable`: This host buffer must have been
596                        // initially constructed with a mutable pointer.
597                        // Evidence: See `create_memory_region_of_account`. Direct mapping case
598                        // later reconstructs this buffer from scratch. See below.
599                        region.redirect(region.host_buffer().mutable());
600                    }
601                }
602
603                // Potentially unshare / make the account shared data unique (CoW logic).
604                if virtual_address_space_adjustments && account_data_direct_mapping {
605                    unsafe {
606                        // SAFETY: refer to the comment above.
607                        region.redirect(account.raw_mut_data_slice());
608                    }
609                }
610            },
611        )
612    }
613
614    /// Take ownership of the instruction trace
615    pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
616        // The last frame is a placeholder for the next instruction to be executed, so it
617        // is empty.
618        self.instruction_trace.pop();
619        (
620            std::mem::take(&mut self.instruction_trace),
621            std::mem::take(&mut self.instruction_accounts),
622            std::mem::take(&mut self.instruction_data),
623        )
624    }
625
626    /// Called instruction are those that the program runtime has already called into. It
627    /// encompasses instructions under execution (e.g. all nested CPIs are already called) and
628    /// finished ones.
629    ///
630    /// Top level instructions that have not yet been executed aren't considered called.
631    pub fn number_of_called_instructions_in_trace(&self) -> usize {
632        self.next_top_level_instruction_index
633            .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
634    }
635
636    /// Return next top level instruction to execute
637    pub fn next_top_level_instruction_index(&self) -> usize {
638        self.next_top_level_instruction_index
639    }
640
641    /// Return number of CPIs in instruction trace
642    pub fn number_of_cpis_in_trace(&self) -> usize {
643        self.transaction_frame.number_of_cpis_in_trace as usize
644    }
645}
646
647/// Return data at the end of a transaction
648#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
649#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
650#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
651#[derive(Clone, Debug, Default, PartialEq, Eq)]
652pub struct TransactionReturnData {
653    pub program_id: Pubkey,
654    pub data: Vec<u8>,
655}
656
657/// Everything that needs to be recorded from a TransactionContext after execution
658#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
659pub struct ExecutionRecord {
660    pub accounts: Vec<KeyedAccountSharedData>,
661    pub return_data: TransactionReturnData,
662    /// Parallel to `accounts`: whether each account was modified by the VM.
663    pub touched_flags: Box<[bool]>,
664    pub accounts_resize_delta: i64,
665}
666
667/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
668#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
669impl From<TransactionContext<'_>> for ExecutionRecord {
670    fn from(context: TransactionContext) -> Self {
671        let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
672            .expect("transaction_context.accounts has unexpected outstanding refs")
673            .take();
674
675        // The flags only needed interior mutability while the VM was running.
676        // Now that we own them, unwrap the per-element `Cell`s into a plain
677        // `Box<[bool]>`. `Vec::from` reuses the box's allocation and the mapped
678        // collect reuses that same buffer in place (`Cell<bool>` and `bool` have
679        // identical layout), so no reallocation occurs.
680        let touched_flags: Box<[bool]> = Vec::from(touched_flags)
681            .into_iter()
682            .map(|flag| flag.into_inner())
683            .collect();
684
685        let return_data = TransactionReturnData {
686            program_id: context.transaction_frame.return_data_pubkey,
687            data: context.return_data_bytes,
688        };
689
690        Self {
691            accounts,
692            return_data,
693            touched_flags,
694            accounts_resize_delta: Cell::into_inner(resize_delta),
695        }
696    }
697}
698
699#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
700mod tests {
701    use super::*;
702
703    #[test]
704    fn test_instructions_sysvar_store_index_checked() {
705        let build_transaction_context = |account: AccountSharedData| {
706            TransactionContext::new(
707                vec![
708                    (Pubkey::new_unique(), AccountSharedData::default()),
709                    (instructions::id(), account),
710                ],
711                Rent::default(),
712                /* max_instruction_stack_depth */ 2,
713                /* max_instruction_trace_length */ 2,
714                /* number_of_top_level_instructions */ 1,
715            )
716        };
717
718        let correct_space = 2;
719        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
720
721        // First try it with the wrong owner.
722        let account =
723            AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
724        assert_eq!(
725            build_transaction_context(account).push(),
726            Err(InstructionError::InvalidAccountOwner),
727        );
728
729        // Now with the wrong data length.
730        let account =
731            AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
732        assert_eq!(
733            build_transaction_context(account).push(),
734            Err(InstructionError::AccountDataTooSmall),
735        );
736
737        // Finally provide the correct account setup.
738        let account = AccountSharedData::new(
739            rent_exempt_lamports,
740            correct_space,
741            &solana_sdk_ids::sysvar::id(),
742        );
743        assert_eq!(build_transaction_context(account).push(), Ok(()),);
744    }
745
746    #[test]
747    fn test_invalid_native_loader_index() {
748        let mut transaction_context = TransactionContext::new(
749            vec![(
750                Pubkey::new_unique(),
751                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
752            )],
753            Rent::default(),
754            20,
755            20,
756            1,
757        );
758
759        transaction_context
760            .configure_top_level_instruction_for_tests(
761                u16::MAX,
762                vec![InstructionAccount::new(0, false, false)],
763                vec![],
764            )
765            .unwrap();
766        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
767
768        let result = instruction_context.get_index_of_program_account_in_transaction();
769        assert_eq!(result, Err(InstructionError::MissingAccount));
770
771        let result = instruction_context.get_program_key();
772        assert_eq!(result, Err(InstructionError::MissingAccount));
773
774        let result = instruction_context.get_program_owner();
775        assert_eq!(result.err(), Some(InstructionError::MissingAccount));
776    }
777
778    #[test]
779    fn test_instruction_shared_items() {
780        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 10];
781        let mut transaction_context =
782            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
783
784        let instruction_accounts_1 = vec![
785            InstructionAccount::new(0, false, true),
786            InstructionAccount::new(3, true, false),
787        ];
788        transaction_context
789            .configure_top_level_instruction_for_tests(
790                1,
791                instruction_accounts_1.clone(),
792                vec![1, 2, 3, 4],
793            )
794            .unwrap();
795        transaction_context.push().unwrap();
796
797        let instruction_accounts_2 = vec![
798            InstructionAccount::new(0, false, true),
799            InstructionAccount::new(3, true, false),
800            InstructionAccount::new(5, false, false),
801        ];
802        transaction_context
803            .configure_top_level_instruction_for_tests(
804                1,
805                instruction_accounts_2.clone(),
806                vec![5, 6, 7, 8, 9],
807            )
808            .unwrap();
809        transaction_context.push().unwrap();
810
811        let instruction_accounts_3 = vec![
812            InstructionAccount::new(0, false, true),
813            InstructionAccount::new(3, true, false),
814            InstructionAccount::new(5, false, false),
815            InstructionAccount::new(3, false, false),
816            InstructionAccount::new(10, false, false),
817        ];
818        transaction_context
819            .configure_top_level_instruction_for_tests(
820                1,
821                instruction_accounts_3.clone(),
822                vec![10, 11],
823            )
824            .unwrap();
825        transaction_context.push().unwrap();
826
827        let first_ix_context = transaction_context
828            .get_instruction_context_at_index_in_trace(0)
829            .unwrap();
830        assert_eq!(
831            instruction_accounts_1.as_slice(),
832            first_ix_context.instruction_accounts
833        );
834        assert_eq!(
835            *first_ix_context.instruction_data,
836            **transaction_context.instruction_data.first().unwrap()
837        );
838        for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
839            assert_eq!(
840                *first_ix_context
841                    .dedup_map
842                    .get(acc.index_in_transaction as usize)
843                    .unwrap(),
844                idx_in_ix as u16
845            );
846        }
847
848        let second_ix_context = transaction_context
849            .get_instruction_context_at_index_in_trace(1)
850            .unwrap();
851        assert_eq!(
852            instruction_accounts_2.as_slice(),
853            second_ix_context.instruction_accounts
854        );
855        assert_eq!(
856            *second_ix_context.instruction_data,
857            **transaction_context.instruction_data.get(1).unwrap()
858        );
859        for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
860            assert_eq!(
861                *second_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        let third_ix_context = transaction_context
870            .get_instruction_context_at_index_in_trace(2)
871            .unwrap();
872        assert_eq!(
873            instruction_accounts_3.as_slice(),
874            third_ix_context.instruction_accounts
875        );
876        assert_eq!(
877            *third_ix_context.instruction_data,
878            **transaction_context.instruction_data.get(2).unwrap()
879        );
880        for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
881            if idx_in_ix == 3 {
882                assert_eq!(
883                    *third_ix_context
884                        .dedup_map
885                        .get(acc.index_in_transaction as usize)
886                        .unwrap(),
887                    1
888                );
889            } else {
890                assert_eq!(
891                    *third_ix_context
892                        .dedup_map
893                        .get(acc.index_in_transaction as usize)
894                        .unwrap(),
895                    idx_in_ix as u16
896                );
897            }
898        }
899    }
900
901    #[test]
902    fn test_number_of_instructions() {
903        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
904        let mut transaction_context =
905            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
906        assert_eq!(
907            transaction_context
908                .transaction_frame
909                .number_of_cpis_in_trace,
910            0
911        );
912
913        // Instruction #0
914        transaction_context
915            .configure_instruction_at_index(
916                0,
917                0,
918                vec![InstructionAccount::new(1, false, false)],
919                vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
920                Vec::new().into(),
921                None,
922            )
923            .unwrap();
924
925        // Instruction #1
926        transaction_context
927            .configure_instruction_at_index(
928                1,
929                0,
930                vec![InstructionAccount::new(1, false, false)],
931                vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
932                Vec::new().into(),
933                None,
934            )
935            .unwrap();
936
937        // Executing instruction #0
938        transaction_context.push().unwrap();
939        assert_eq!(
940            transaction_context
941                .transaction_frame
942                .current_executing_instruction,
943            0
944        );
945        assert_eq!(
946            transaction_context.number_of_called_instructions_in_trace(),
947            1
948        );
949
950        assert_eq!(
951            transaction_context
952                .transaction_frame
953                .total_number_of_instructions_in_trace,
954            2
955        );
956
957        assert_eq!(
958            transaction_context
959                .transaction_frame
960                .number_of_cpis_in_trace,
961            0
962        );
963
964        assert_eq!(
965            transaction_context
966                .transaction_frame
967                .cpi_data_scratchpad
968                .ptr(),
969            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
970        );
971        assert_eq!(
972            transaction_context
973                .transaction_frame
974                .cpi_data_scratchpad
975                .len(),
976            0,
977        );
978        assert_eq!(
979            transaction_context
980                .transaction_frame
981                .cpi_accounts_scratchpad
982                .ptr(),
983            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
984                .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
985        );
986        assert_eq!(
987            transaction_context
988                .transaction_frame
989                .cpi_data_scratchpad
990                .len(),
991            0,
992        );
993
994        assert_eq!(
995            transaction_context.number_of_called_instructions_in_trace(),
996            1
997        );
998
999        // Instruction #0 does a CPI.
1000        transaction_context
1001            .configure_next_cpi_for_tests(
1002                0,
1003                vec![InstructionAccount::new(2, false, true)],
1004                Vec::new(),
1005            )
1006            .unwrap();
1007
1008        transaction_context.push().unwrap();
1009        assert_eq!(
1010            transaction_context
1011                .transaction_frame
1012                .current_executing_instruction,
1013            2
1014        );
1015
1016        assert_eq!(
1017            transaction_context
1018                .transaction_frame
1019                .total_number_of_instructions_in_trace,
1020            3
1021        );
1022        assert_eq!(
1023            transaction_context
1024                .transaction_frame
1025                .number_of_cpis_in_trace,
1026            1
1027        );
1028        assert_eq!(
1029            transaction_context.number_of_called_instructions_in_trace(),
1030            2
1031        );
1032
1033        assert_eq!(
1034            transaction_context
1035                .transaction_frame
1036                .cpi_data_scratchpad
1037                .ptr(),
1038            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1039        );
1040        assert_eq!(
1041            transaction_context
1042                .transaction_frame
1043                .cpi_accounts_scratchpad
1044                .ptr(),
1045            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1046                .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1047        );
1048
1049        // A nested CPI
1050        transaction_context
1051            .configure_next_cpi_for_tests(
1052                0,
1053                vec![InstructionAccount::new(2, false, true)],
1054                Vec::new(),
1055            )
1056            .unwrap();
1057
1058        transaction_context.push().unwrap();
1059        assert_eq!(
1060            transaction_context
1061                .transaction_frame
1062                .current_executing_instruction,
1063            3
1064        );
1065
1066        assert_eq!(
1067            transaction_context
1068                .transaction_frame
1069                .total_number_of_instructions_in_trace,
1070            4
1071        );
1072
1073        assert_eq!(
1074            transaction_context
1075                .transaction_frame
1076                .cpi_data_scratchpad
1077                .ptr(),
1078            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1079        );
1080        assert_eq!(
1081            transaction_context
1082                .transaction_frame
1083                .cpi_accounts_scratchpad
1084                .ptr(),
1085            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1086                .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1087        );
1088
1089        assert_eq!(
1090            transaction_context
1091                .transaction_frame
1092                .number_of_cpis_in_trace,
1093            2
1094        );
1095
1096        assert_eq!(
1097            transaction_context.number_of_called_instructions_in_trace(),
1098            3
1099        );
1100        // Return from nested CPI
1101        transaction_context.pop().unwrap();
1102        assert_eq!(
1103            transaction_context.number_of_called_instructions_in_trace(),
1104            3
1105        );
1106
1107        assert_eq!(
1108            transaction_context
1109                .transaction_frame
1110                .total_number_of_instructions_in_trace,
1111            4
1112        );
1113        assert_eq!(
1114            transaction_context
1115                .transaction_frame
1116                .number_of_cpis_in_trace,
1117            2,
1118        );
1119        assert_eq!(
1120            transaction_context
1121                .transaction_frame
1122                .current_executing_instruction,
1123            2
1124        );
1125
1126        // A second nested CPI
1127        transaction_context
1128            .configure_next_cpi_for_tests(
1129                0,
1130                vec![InstructionAccount::new(2, false, true)],
1131                Vec::new(),
1132            )
1133            .unwrap();
1134
1135        transaction_context.push().unwrap();
1136        assert_eq!(
1137            transaction_context
1138                .transaction_frame
1139                .current_executing_instruction,
1140            4
1141        );
1142
1143        assert_eq!(
1144            transaction_context
1145                .transaction_frame
1146                .total_number_of_instructions_in_trace,
1147            5
1148        );
1149
1150        assert_eq!(
1151            transaction_context
1152                .transaction_frame
1153                .cpi_data_scratchpad
1154                .ptr(),
1155            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1156        );
1157        assert_eq!(
1158            transaction_context
1159                .transaction_frame
1160                .cpi_accounts_scratchpad
1161                .ptr(),
1162            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1163                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1164        );
1165
1166        assert_eq!(
1167            transaction_context
1168                .transaction_frame
1169                .number_of_cpis_in_trace,
1170            3
1171        );
1172        assert_eq!(
1173            transaction_context.number_of_called_instructions_in_trace(),
1174            4
1175        );
1176
1177        // Return from second nested CPI
1178        transaction_context.pop().unwrap();
1179
1180        assert_eq!(
1181            transaction_context
1182                .transaction_frame
1183                .current_executing_instruction,
1184            2
1185        );
1186
1187        assert_eq!(
1188            transaction_context
1189                .transaction_frame
1190                .total_number_of_instructions_in_trace,
1191            5
1192        );
1193
1194        assert_eq!(
1195            transaction_context
1196                .transaction_frame
1197                .cpi_data_scratchpad
1198                .ptr(),
1199            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1200        );
1201        assert_eq!(
1202            transaction_context
1203                .transaction_frame
1204                .cpi_accounts_scratchpad
1205                .ptr(),
1206            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1207                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1208        );
1209
1210        assert_eq!(
1211            transaction_context
1212                .transaction_frame
1213                .number_of_cpis_in_trace,
1214            3
1215        );
1216
1217        // Return from first CPI
1218        transaction_context.pop().unwrap();
1219        assert_eq!(
1220            transaction_context.number_of_called_instructions_in_trace(),
1221            4
1222        );
1223
1224        assert_eq!(
1225            transaction_context
1226                .transaction_frame
1227                .current_executing_instruction,
1228            0
1229        );
1230
1231        assert_eq!(
1232            transaction_context
1233                .transaction_frame
1234                .total_number_of_instructions_in_trace,
1235            5
1236        );
1237
1238        assert_eq!(
1239            transaction_context
1240                .transaction_frame
1241                .cpi_data_scratchpad
1242                .ptr(),
1243            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1244        );
1245        assert_eq!(
1246            transaction_context
1247                .transaction_frame
1248                .cpi_accounts_scratchpad
1249                .ptr(),
1250            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1251                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1252        );
1253
1254        assert_eq!(
1255            transaction_context
1256                .transaction_frame
1257                .number_of_cpis_in_trace,
1258            3,
1259        );
1260
1261        // Let's go to Instruction #1 (top level)
1262        transaction_context.pop().unwrap();
1263        transaction_context.push().unwrap();
1264        assert_eq!(
1265            transaction_context
1266                .transaction_frame
1267                .current_executing_instruction,
1268            1,
1269        );
1270        assert_eq!(
1271            transaction_context
1272                .transaction_frame
1273                .number_of_cpis_in_trace,
1274            3
1275        );
1276
1277        // Instruction #1 will do a CPI.
1278        transaction_context
1279            .configure_next_cpi_for_tests(
1280                0,
1281                vec![InstructionAccount::new(2, false, true)],
1282                Vec::new(),
1283            )
1284            .unwrap();
1285
1286        transaction_context.push().unwrap();
1287
1288        assert_eq!(
1289            transaction_context
1290                .transaction_frame
1291                .current_executing_instruction,
1292            5,
1293        );
1294
1295        assert_eq!(
1296            transaction_context
1297                .transaction_frame
1298                .total_number_of_instructions_in_trace,
1299            6
1300        );
1301
1302        assert_eq!(
1303            transaction_context
1304                .transaction_frame
1305                .cpi_data_scratchpad
1306                .ptr(),
1307            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1308        );
1309        assert_eq!(
1310            transaction_context
1311                .transaction_frame
1312                .cpi_accounts_scratchpad
1313                .ptr(),
1314            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1315                .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1316        );
1317
1318        assert_eq!(
1319            transaction_context
1320                .transaction_frame
1321                .number_of_cpis_in_trace,
1322            4
1323        );
1324        assert_eq!(
1325            transaction_context.number_of_called_instructions_in_trace(),
1326            6
1327        );
1328
1329        // Return from CPI
1330        transaction_context.pop().unwrap();
1331        assert_eq!(
1332            transaction_context
1333                .transaction_frame
1334                .number_of_cpis_in_trace,
1335            4
1336        );
1337        assert_eq!(
1338            transaction_context
1339                .transaction_frame
1340                .current_executing_instruction,
1341            1,
1342        );
1343
1344        transaction_context.pop().unwrap();
1345    }
1346
1347    #[test]
1348    fn test_get_current_instruction_index() {
1349        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1350        let mut transaction_context =
1351            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1352
1353        // First top level instruction
1354        transaction_context
1355            .configure_instruction_at_index(
1356                0,
1357                1,
1358                vec![
1359                    InstructionAccount::new(0, false, false),
1360                    InstructionAccount::new(1, false, false),
1361                ],
1362                vec![u16::MAX; 256],
1363                Cow::Owned(Vec::new()),
1364                None,
1365            )
1366            .unwrap();
1367
1368        // Second top-level instruction
1369        transaction_context
1370            .configure_instruction_at_index(
1371                1,
1372                1,
1373                vec![
1374                    InstructionAccount::new(0, false, false),
1375                    InstructionAccount::new(1, false, true),
1376                ],
1377                vec![u16::MAX; 256],
1378                Cow::Owned(Vec::new()),
1379                None,
1380            )
1381            .unwrap();
1382
1383        transaction_context.push().unwrap();
1384        assert_eq!(
1385            transaction_context.get_current_instruction_index().unwrap(),
1386            0
1387        );
1388
1389        transaction_context.pop().unwrap();
1390
1391        transaction_context.push().unwrap();
1392        assert_eq!(
1393            transaction_context.get_current_instruction_index().unwrap(),
1394            1
1395        );
1396
1397        // Simulating a CPI
1398        transaction_context
1399            .configure_next_cpi_for_tests(
1400                1,
1401                vec![
1402                    InstructionAccount::new(0, false, true),
1403                    InstructionAccount::new(1, false, false),
1404                ],
1405                Vec::new(),
1406            )
1407            .unwrap();
1408        transaction_context.push().unwrap();
1409        assert_eq!(
1410            transaction_context.get_current_instruction_index().unwrap(),
1411            2
1412        );
1413
1414        // Yet another CPI
1415        transaction_context
1416            .configure_next_cpi_for_tests(
1417                1,
1418                vec![
1419                    InstructionAccount::new(0, false, true),
1420                    InstructionAccount::new(1, false, false),
1421                ],
1422                Vec::new(),
1423            )
1424            .unwrap();
1425        transaction_context.push().unwrap();
1426        assert_eq!(
1427            transaction_context.get_current_instruction_index().unwrap(),
1428            3
1429        );
1430
1431        // CPI return
1432        transaction_context.pop().unwrap();
1433        assert_eq!(
1434            transaction_context.get_current_instruction_index().unwrap(),
1435            2
1436        );
1437
1438        // CPI return 2
1439        transaction_context.pop().unwrap();
1440        assert_eq!(
1441            transaction_context.get_current_instruction_index().unwrap(),
1442            1
1443        );
1444    }
1445}