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, metrics::MetricsCollection, processor::Processor,
25        transaction::TransactionMetadata,
26    },
27    async_trait::async_trait,
28    serde::Deserialize,
29    solana_sdk::{instruction::AccountMeta, pubkey::Pubkey},
30    std::{ops::Deref, sync::Arc},
31};
32
33/// Metadata associated with a specific instruction, including transaction-level
34/// details.
35///
36/// `InstructionMetadata` is utilized within the pipeline to associate each
37/// instruction with the broader context of its transaction, as well as its
38/// position within the instruction stack.
39///
40/// # Fields
41///
42/// - `transaction_metadata`: Metadata providing details of the entire
43///   transaction.
44/// - `stack_height`: Represents the instruction’s depth within the stack, where
45///   0 is the root level.
46
47#[derive(Debug, Clone)]
48pub struct InstructionMetadata {
49    pub transaction_metadata: TransactionMetadata,
50    pub stack_height: u32,
51}
52
53pub type InstructionsWithMetadata =
54    Vec<(InstructionMetadata, solana_sdk::instruction::Instruction)>;
55
56/// A decoded instruction containing program ID, data, and associated accounts.
57///
58/// The `DecodedInstruction` struct represents the outcome of decoding a raw
59/// instruction, encapsulating its program ID, parsed data, and the accounts
60/// involved.
61///
62/// # Type Parameters
63///
64/// - `T`: The type representing the decoded data for the instruction.
65///
66/// # Fields
67///
68/// - `program_id`: The program ID that owns the instruction.
69/// - `data`: The decoded data payload for the instruction, of type `T`.
70/// - `accounts`: A vector of `AccountMeta`, representing the accounts involved
71///   in the instruction.
72
73#[derive(Debug, Clone, Deserialize)]
74pub struct DecodedInstruction<T> {
75    pub program_id: Pubkey,
76    pub data: T,
77    pub accounts: Vec<AccountMeta>,
78}
79
80/// A trait for decoding Solana instructions into a structured type.
81///
82/// Implement the `InstructionDecoder` trait for types that can decode raw
83/// instructions into a more meaningful structure, providing
84/// application-specific logic.
85///
86/// # Type Parameters
87///
88/// - `InstructionType`: The type into which the instruction data will be
89///   decoded.
90///
91/// # Required Methods
92///
93/// - `decode_instruction`: Decodes a raw Solana `Instruction` into a
94///   `DecodedInstruction`.
95pub trait InstructionDecoder<'a> {
96    type InstructionType;
97
98    fn decode_instruction(
99        &self,
100        instruction: &'a solana_sdk::instruction::Instruction,
101    ) -> Option<DecodedInstruction<Self::InstructionType>>;
102}
103
104/// The input type for the instruction processor.
105///
106/// - `T`: The instruction type
107pub type InstructionProcessorInputType<T> = (
108    InstructionMetadata,
109    DecodedInstruction<T>,
110    Vec<NestedInstruction>,
111);
112
113/// A processing pipeline for instructions, using a decoder and processor.
114///
115/// The `InstructionPipe` structure enables the processing of decoded
116/// instructions, pairing an `InstructionDecoder` with a `Processor`. It
117/// supports generic instruction types.
118///
119/// # Type Parameters
120///
121/// - `T`: The type representing the decoded instruction data.
122///
123/// # Fields
124///
125/// - `decoder`: The decoder used for parsing instructions.
126/// - `processor`: The processor that handles decoded instructions.
127pub struct InstructionPipe<T: Send> {
128    pub decoder:
129        Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
130    pub processor:
131        Box<dyn Processor<InputType = InstructionProcessorInputType<T>> + Send + Sync + 'static>,
132}
133
134/// An async trait for processing instructions within nested contexts.
135///
136/// The `InstructionPipes` trait allows for recursive processing of instructions
137/// that may contain nested instructions. This enables complex, hierarchical
138/// instruction handling for transactions.
139///
140/// # Required Methods
141///
142/// - `run`: Processes a `NestedInstruction`, recursively processing any inner
143///   instructions.
144#[async_trait]
145pub trait InstructionPipes<'a>: Send + Sync {
146    async fn run(
147        &mut self,
148        nested_instruction: &NestedInstruction,
149        metrics: Arc<MetricsCollection>,
150    ) -> CarbonResult<()>;
151}
152
153#[async_trait]
154impl<T: Send + 'static> InstructionPipes<'_> for InstructionPipe<T> {
155    async fn run(
156        &mut self,
157        nested_instruction: &NestedInstruction,
158        metrics: Arc<MetricsCollection>,
159    ) -> CarbonResult<()> {
160        log::trace!(
161            "InstructionPipe::run(nested_instruction: {:?}, metrics)",
162            nested_instruction,
163        );
164
165        if let Some(decoded_instruction) = self
166            .decoder
167            .decode_instruction(&nested_instruction.instruction)
168        {
169            self.processor
170                .process(
171                    (
172                        nested_instruction.metadata.clone(),
173                        decoded_instruction,
174                        nested_instruction.inner_instructions.clone(),
175                    ),
176                    metrics.clone(),
177                )
178                .await?;
179        }
180
181        for nested_inner_instruction in nested_instruction.inner_instructions.iter() {
182            self.run(nested_inner_instruction, metrics.clone()).await?;
183        }
184
185        Ok(())
186    }
187}
188
189/// Represents a nested instruction with metadata, including potential inner
190/// instructions.
191///
192/// The `NestedInstruction` struct allows for recursive instruction handling,
193/// where each instruction may have associated metadata and a list of nested
194/// instructions.
195///
196/// # Fields
197///
198/// - `metadata`: The metadata associated with the instruction.
199/// - `instruction`: The Solana instruction being processed.
200/// - `inner_instructions`: A vector of `NestedInstruction`, representing any
201///   nested instructions.
202#[derive(Debug, Clone)]
203pub struct NestedInstruction {
204    pub metadata: InstructionMetadata,
205    pub instruction: solana_sdk::instruction::Instruction,
206    pub inner_instructions: Vec<NestedInstruction>,
207}
208
209#[derive(Debug)]
210pub struct NestedInstructions(pub Vec<NestedInstruction>);
211
212impl NestedInstructions {
213    pub fn iter(&self) -> std::slice::Iter<NestedInstruction> {
214        self.0.iter()
215    }
216}
217
218impl Deref for NestedInstructions {
219    type Target = [NestedInstruction];
220
221    fn deref(&self) -> &[NestedInstruction] {
222        &self.0[..]
223    }
224}
225
226/// Nests instructions based on stack height, producing a hierarchy of
227/// `NestedInstruction`.
228///
229/// This function organizes instructions into a nested structure, enabling
230/// hierarchical transaction analysis. Instructions are nested according to
231/// their stack height, forming a tree-like structure.
232///
233/// # Parameters
234///
235/// - `instructions`: A list of tuples containing `InstructionMetadata` and
236///   instructions.
237///
238/// # Returns
239///
240/// A vector of `NestedInstruction`, representing the instructions organized by
241/// stack depth.
242impl From<InstructionsWithMetadata> for NestedInstructions {
243    fn from(instructions: InstructionsWithMetadata) -> Self {
244        log::trace!("from(instructions: {:?})", instructions);
245        let mut result = Vec::<NestedInstruction>::new();
246        let mut stack = Vec::<(Vec<usize>, usize)>::new();
247
248        for (metadata, instruction) in instructions {
249            let nested_instruction = NestedInstruction {
250                metadata: metadata.clone(),
251                instruction,
252                inner_instructions: Vec::new(),
253            };
254
255            while let Some((_, parent_stack_height)) = stack.last() {
256                if metadata.stack_height as usize > *parent_stack_height {
257                    break;
258                }
259                stack.pop();
260            }
261
262            if let Some((path_to_parent, _)) = stack.last() {
263                let mut current_instructions = &mut result;
264                for &index in path_to_parent {
265                    current_instructions = &mut current_instructions[index].inner_instructions;
266                }
267                current_instructions.push(nested_instruction);
268                let mut new_path = path_to_parent.clone();
269                new_path.push(current_instructions.len() - 1);
270                stack.push((new_path, metadata.stack_height as usize));
271            } else {
272                result.push(nested_instruction);
273                let new_path = vec![result.len() - 1];
274                stack.push((new_path, metadata.stack_height as usize));
275            }
276        }
277
278        NestedInstructions(result)
279    }
280}