carbon_core/
instruction.rs

1//! Provides structures and traits for decoding and processing instructions
2//! within transactions.
3//!
4//! The module includes the following main components:
5//! - **`InstructionMetadata`**: Metadata associated with an instruction,
6//!   capturing transaction context.
7//! - **`DecodedInstruction`**: Represents an instruction that has been decoded,
8//!   with associated program ID, data, and accounts.
9//! - **`InstructionDecoder`**: A trait for decoding instructions into specific
10//!   types.
11//! - **`InstructionPipe`**: A structure that processes instructions using a
12//!   decoder and a processor.
13//! - **`InstructionPipes`**: An async trait for processing instructions within
14//!   nested contexts.
15//! - **`NestedInstruction`**: Represents instructions with potential nested
16//!   inner instructions, allowing for recursive processing.
17//!
18//! These components enable the `carbon-core` framework to handle Solana
19//! transaction instructions efficiently, decoding them into structured types
20//! and facilitating hierarchical processing.
21
22use {
23    crate::{
24        error::CarbonResult, filter::Filter, metrics::MetricsCollection, processor::Processor,
25        transaction::TransactionMetadata,
26    },
27    async_trait::async_trait,
28    serde::{Deserialize, Serialize},
29    solana_instruction::AccountMeta,
30    solana_pubkey::Pubkey,
31    std::{
32        ops::{Deref, DerefMut},
33        sync::Arc,
34    },
35};
36
37/// Metadata associated with a specific instruction, including transaction-level
38/// details.
39///
40/// `InstructionMetadata` is utilized within the pipeline to associate each
41/// instruction with the broader context of its transaction, as well as its
42/// position within the instruction stack.
43///
44/// # Fields
45///
46/// - `transaction_metadata`: Metadata providing details of the entire
47///   transaction.
48/// - `stack_height`: Represents the instruction's depth within the stack, where
49///   1 is the root level.
50/// - `index`: The index of the instruction in the transaction. The index is
51///   relative within stack height and is 1-based. Note that the inner
52///   instruction indexes are grouped into one vector, so different inner
53///   instructions that have different stack heights may have continuous
54///   indexes.
55
56#[derive(Debug, Clone)]
57pub struct InstructionMetadata {
58    pub transaction_metadata: Arc<TransactionMetadata>,
59    pub stack_height: u32,
60    pub index: u32,
61    pub absolute_path: Vec<u8>,
62}
63
64pub type InstructionsWithMetadata = Vec<(InstructionMetadata, solana_instruction::Instruction)>;
65
66/// A decoded instruction containing program ID, data, and associated accounts.
67///
68/// The `DecodedInstruction` struct represents the outcome of decoding a raw
69/// instruction, encapsulating its program ID, parsed data, and the accounts
70/// involved.
71///
72/// # Type Parameters
73///
74/// - `T`: The type representing the decoded data for the instruction.
75///
76/// # Fields
77///
78/// - `program_id`: The program ID that owns the instruction.
79/// - `data`: The decoded data payload for the instruction, of type `T`.
80/// - `accounts`: A vector of `AccountMeta`, representing the accounts involved
81///   in the instruction.
82
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct DecodedInstruction<T> {
85    pub program_id: Pubkey,
86    pub data: T,
87    pub accounts: Vec<AccountMeta>,
88}
89
90/// A trait for decoding Solana instructions into a structured type.
91///
92/// Implement the `InstructionDecoder` trait for types that can decode raw
93/// instructions into a more meaningful structure, providing
94/// application-specific logic.
95///
96/// # Type Parameters
97///
98/// - `InstructionType`: The type into which the instruction data will be
99///   decoded.
100///
101/// # Required Methods
102///
103/// - `decode_instruction`: Decodes a raw Solana `Instruction` into a
104///   `DecodedInstruction`.
105pub trait InstructionDecoder<'a> {
106    type InstructionType;
107
108    fn decode_instruction(
109        &self,
110        instruction: &'a solana_instruction::Instruction,
111    ) -> Option<DecodedInstruction<Self::InstructionType>>;
112}
113
114/// The input type for the instruction processor.
115///
116/// - `T`: The instruction type
117pub type InstructionProcessorInputType<T> = (
118    InstructionMetadata,
119    DecodedInstruction<T>,
120    NestedInstructions,
121    solana_instruction::Instruction,
122);
123
124/// A processing pipeline for instructions, using a decoder and processor.
125///
126/// The `InstructionPipe` structure enables the processing of decoded
127/// instructions, pairing an `InstructionDecoder` with a `Processor`. It
128/// supports generic instruction types.
129///
130/// # Type Parameters
131///
132/// - `T`: The type representing the decoded instruction data.
133///
134/// # Fields
135///
136/// - `decoder`: The decoder used for parsing instructions.
137/// - `processor`: The processor that handles decoded instructions.
138/// - `filters`: A collection of filters that determine which instruction
139///   updates should be processed. Each filter in this collection is applied to
140///   incoming instruction updates, and only updates that pass all filters
141///   (return `true`) will be processed. If this collection is empty, all
142///   updates are processed.
143pub struct InstructionPipe<T: Send> {
144    pub decoder:
145        Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
146    pub processor:
147        Box<dyn Processor<InputType = InstructionProcessorInputType<T>> + Send + Sync + 'static>,
148    pub filters: Vec<Box<dyn Filter + Send + Sync + 'static>>,
149}
150
151/// An async trait for processing instructions within nested contexts.
152///
153/// The `InstructionPipes` trait allows for recursive processing of instructions
154/// that may contain nested instructions. This enables complex, hierarchical
155/// instruction handling for transactions.
156///
157/// # Required Methods
158///
159/// - `run`: Processes a `NestedInstruction`, recursively processing any inner
160///   instructions.
161/// - `filters`: Returns a reference to the filters associated with this pipe,
162///   which are used by the pipeline to determine which instruction updates
163///   should be processed.
164#[async_trait]
165pub trait InstructionPipes<'a>: Send + Sync {
166    async fn run(
167        &mut self,
168        nested_instruction: &NestedInstruction,
169        metrics: Arc<MetricsCollection>,
170    ) -> CarbonResult<()>;
171    fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>>;
172}
173
174#[async_trait]
175impl<T: Send + 'static> InstructionPipes<'_> for InstructionPipe<T> {
176    async fn run(
177        &mut self,
178        nested_instruction: &NestedInstruction,
179        metrics: Arc<MetricsCollection>,
180    ) -> CarbonResult<()> {
181        log::trace!(
182            "InstructionPipe::run(nested_instruction: {:?}, metrics)",
183            nested_instruction,
184        );
185
186        if let Some(decoded_instruction) = self
187            .decoder
188            .decode_instruction(&nested_instruction.instruction)
189        {
190            self.processor
191                .process(
192                    (
193                        nested_instruction.metadata.clone(),
194                        decoded_instruction,
195                        nested_instruction.inner_instructions.clone(),
196                        nested_instruction.instruction.clone(),
197                    ),
198                    metrics.clone(),
199                )
200                .await?;
201        }
202
203        for nested_inner_instruction in nested_instruction.inner_instructions.iter() {
204            self.run(nested_inner_instruction, metrics.clone()).await?;
205        }
206
207        Ok(())
208    }
209
210    fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>> {
211        &self.filters
212    }
213}
214
215/// Represents a nested instruction with metadata, including potential inner
216/// instructions.
217///
218/// The `NestedInstruction` struct allows for recursive instruction handling,
219/// where each instruction may have associated metadata and a list of nested
220/// instructions.
221///
222/// # Fields
223///
224/// - `metadata`: The metadata associated with the instruction.
225/// - `instruction`: The Solana instruction being processed.
226/// - `inner_instructions`: A vector of `NestedInstruction`, representing any
227///   nested instructions.
228#[derive(Debug, Clone)]
229pub struct NestedInstruction {
230    pub metadata: InstructionMetadata,
231    pub instruction: solana_instruction::Instruction,
232    pub inner_instructions: NestedInstructions,
233}
234
235#[derive(Debug, Default)]
236pub struct NestedInstructions(pub Vec<NestedInstruction>);
237
238impl NestedInstructions {
239    pub fn len(&self) -> usize {
240        self.0.len()
241    }
242
243    pub fn is_empty(&self) -> bool {
244        self.len() == 0
245    }
246
247    pub fn push(&mut self, nested_instruction: NestedInstruction) {
248        self.0.push(nested_instruction);
249    }
250}
251
252impl Deref for NestedInstructions {
253    type Target = [NestedInstruction];
254
255    fn deref(&self) -> &[NestedInstruction] {
256        &self.0[..]
257    }
258}
259
260impl DerefMut for NestedInstructions {
261    fn deref_mut(&mut self) -> &mut [NestedInstruction] {
262        &mut self.0[..]
263    }
264}
265
266impl Clone for NestedInstructions {
267    fn clone(&self) -> Self {
268        NestedInstructions(self.0.clone())
269    }
270}
271
272impl IntoIterator for NestedInstructions {
273    type Item = NestedInstruction;
274    type IntoIter = std::vec::IntoIter<NestedInstruction>;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.0.into_iter()
278    }
279}
280
281/// Nests instructions based on stack height, producing a hierarchy of
282/// `NestedInstruction`.
283///
284/// This function organizes instructions into a nested structure, enabling
285/// hierarchical transaction analysis. Instructions are nested according to
286/// their stack height, forming a tree-like structure.
287///
288/// # Parameters
289///
290/// - `instructions`: A list of tuples containing `InstructionMetadata` and
291///   instructions.
292///
293/// # Returns
294///
295/// A vector of `NestedInstruction`, representing the instructions organized by
296/// stack depth.
297impl From<InstructionsWithMetadata> for NestedInstructions {
298    fn from(instructions: InstructionsWithMetadata) -> Self {
299        log::trace!("from(instructions: {:?})", instructions);
300
301        // To avoid reallocations that result in dangling pointers.
302        // Therefore the number of "push"s must be calculated to set the capacity
303        let estimated_capacity = instructions
304            .iter()
305            .filter(|(meta, _)| meta.stack_height == 1)
306            .count();
307
308        UnsafeNestedBuilder::new(estimated_capacity).build(instructions)
309    }
310}
311
312// https://github.com/anza-xyz/agave/blob/master/program-runtime/src/execution_budget.rs#L7
313pub const MAX_INSTRUCTION_STACK_DEPTH: usize = 5;
314
315pub struct UnsafeNestedBuilder {
316    nested_ixs: Vec<NestedInstruction>,
317    level_ptrs: [Option<*mut NestedInstruction>; MAX_INSTRUCTION_STACK_DEPTH],
318}
319
320impl UnsafeNestedBuilder {
321    /// ## SAFETY:
322    /// Make sure `capacity` is large enough to avoid capacity expansion caused
323    /// by `push`
324    pub fn new(capacity: usize) -> Self {
325        Self {
326            nested_ixs: Vec::with_capacity(capacity),
327            level_ptrs: [None; MAX_INSTRUCTION_STACK_DEPTH],
328        }
329    }
330
331    pub fn build(mut self, instructions: InstructionsWithMetadata) -> NestedInstructions {
332        for (metadata, instruction) in instructions {
333            let stack_height = metadata.stack_height as usize;
334
335            assert!(stack_height > 0);
336            assert!(stack_height <= MAX_INSTRUCTION_STACK_DEPTH);
337
338            for ptr in &mut self.level_ptrs[stack_height..] {
339                *ptr = None;
340            }
341
342            let new_instruction = NestedInstruction {
343                metadata,
344                instruction,
345                inner_instructions: NestedInstructions::default(),
346            };
347
348            // SAFETY:The following operation is safe.
349            // because:
350            // 1. All pointers come from pre-allocated Vec (no extension)
351            // 2. level_ptr does not guarantee any aliasing
352            // 3. Lifecycle is limited to the build() method
353            unsafe {
354                if stack_height == 1 {
355                    self.nested_ixs.push(new_instruction);
356                    let ptr = self.nested_ixs.last_mut().unwrap_unchecked() as *mut _;
357                    self.level_ptrs[0] = Some(ptr);
358                } else if let Some(parent_ptr) = self.level_ptrs[stack_height - 2] {
359                    (*parent_ptr).inner_instructions.push(new_instruction);
360                    let ptr = (*parent_ptr)
361                        .inner_instructions
362                        .last_mut()
363                        .unwrap_unchecked() as *mut _;
364                    self.level_ptrs[stack_height - 1] = Some(ptr);
365                }
366            }
367        }
368
369        NestedInstructions(self.nested_ixs)
370    }
371}
372
373#[cfg(test)]
374mod tests {
375
376    use {super::*, solana_instruction::Instruction};
377
378    fn create_instruction_with_metadata(
379        index: u32,
380        stack_height: u32,
381    ) -> (InstructionMetadata, Instruction) {
382        let metadata = InstructionMetadata {
383            transaction_metadata: Arc::default(),
384            stack_height,
385            index,
386            absolute_path: vec![],
387        };
388        let instruction = Instruction {
389            program_id: Pubkey::new_unique(),
390            accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
391            data: vec![],
392        };
393        (metadata, instruction)
394    }
395
396    #[test]
397    fn test_nested_instructions_single_level() {
398        let instructions = vec![
399            create_instruction_with_metadata(1, 1),
400            create_instruction_with_metadata(2, 1),
401        ];
402        let nested_instructions: NestedInstructions = instructions.into();
403        assert_eq!(nested_instructions.len(), 2);
404        assert!(nested_instructions[0].inner_instructions.is_empty());
405        assert!(nested_instructions[1].inner_instructions.is_empty());
406    }
407
408    #[test]
409    fn test_nested_instructions_empty() {
410        let instructions: InstructionsWithMetadata = vec![];
411        let nested_instructions: NestedInstructions = instructions.into();
412        assert!(nested_instructions.is_empty());
413    }
414
415    #[test]
416    fn test_deep_nested_instructions() {
417        let instructions = vec![
418            create_instruction_with_metadata(0, 1),
419            create_instruction_with_metadata(0, 1),
420            create_instruction_with_metadata(1, 2),
421            create_instruction_with_metadata(1, 3),
422            create_instruction_with_metadata(1, 3),
423            create_instruction_with_metadata(1, 3),
424        ];
425
426        let nested_instructions: NestedInstructions = instructions.into();
427        assert_eq!(nested_instructions.len(), 2);
428        assert_eq!(nested_instructions.0[1].inner_instructions.len(), 1);
429    }
430}