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