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::{
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 instruction indexes are grouped into one vector,
52///   so different inner instructions that have different stack heights may have continuous indexes.
53///
54
55#[derive(Debug, Clone)]
56pub struct InstructionMetadata {
57    pub transaction_metadata: TransactionMetadata,
58    pub stack_height: u32,
59    pub index: u32,
60}
61
62pub type InstructionsWithMetadata = Vec<(InstructionMetadata, solana_instruction::Instruction)>;
63
64/// A decoded instruction containing program ID, data, and associated accounts.
65///
66/// The `DecodedInstruction` struct represents the outcome of decoding a raw
67/// instruction, encapsulating its program ID, parsed data, and the accounts
68/// involved.
69///
70/// # Type Parameters
71///
72/// - `T`: The type representing the decoded data for the instruction.
73///
74/// # Fields
75///
76/// - `program_id`: The program ID that owns the instruction.
77/// - `data`: The decoded data payload for the instruction, of type `T`.
78/// - `accounts`: A vector of `AccountMeta`, representing the accounts involved
79///   in the instruction.
80
81#[derive(Debug, Clone, Deserialize)]
82pub struct DecodedInstruction<T> {
83    pub program_id: Pubkey,
84    pub data: T,
85    pub accounts: Vec<AccountMeta>,
86}
87
88/// A trait for decoding Solana instructions into a structured type.
89///
90/// Implement the `InstructionDecoder` trait for types that can decode raw
91/// instructions into a more meaningful structure, providing
92/// application-specific logic.
93///
94/// # Type Parameters
95///
96/// - `InstructionType`: The type into which the instruction data will be
97///   decoded.
98///
99/// # Required Methods
100///
101/// - `decode_instruction`: Decodes a raw Solana `Instruction` into a
102///   `DecodedInstruction`.
103pub trait InstructionDecoder<'a> {
104    type InstructionType;
105
106    fn decode_instruction(
107        &self,
108        instruction: &'a solana_instruction::Instruction,
109    ) -> Option<DecodedInstruction<Self::InstructionType>>;
110}
111
112/// The input type for the instruction processor.
113///
114/// - `T`: The instruction type
115pub type InstructionProcessorInputType<T> = (
116    InstructionMetadata,
117    DecodedInstruction<T>,
118    NestedInstructions,
119);
120
121/// A processing pipeline for instructions, using a decoder and processor.
122///
123/// The `InstructionPipe` structure enables the processing of decoded
124/// instructions, pairing an `InstructionDecoder` with a `Processor`. It
125/// supports generic instruction types.
126///
127/// # Type Parameters
128///
129/// - `T`: The type representing the decoded instruction data.
130///
131/// # Fields
132///
133/// - `decoder`: The decoder used for parsing instructions.
134/// - `processor`: The processor that handles decoded instructions.
135pub struct InstructionPipe<T: Send> {
136    pub decoder:
137        Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
138    pub processor:
139        Box<dyn Processor<InputType = InstructionProcessorInputType<T>> + Send + Sync + 'static>,
140}
141
142/// An async trait for processing instructions within nested contexts.
143///
144/// The `InstructionPipes` trait allows for recursive processing of instructions
145/// that may contain nested instructions. This enables complex, hierarchical
146/// instruction handling for transactions.
147///
148/// # Required Methods
149///
150/// - `run`: Processes a `NestedInstruction`, recursively processing any inner
151///   instructions.
152#[async_trait]
153pub trait InstructionPipes<'a>: Send + Sync {
154    async fn run(
155        &mut self,
156        nested_instruction: &NestedInstruction,
157        metrics: Arc<MetricsCollection>,
158    ) -> CarbonResult<()>;
159}
160
161#[async_trait]
162impl<T: Send + 'static> InstructionPipes<'_> for InstructionPipe<T> {
163    async fn run(
164        &mut self,
165        nested_instruction: &NestedInstruction,
166        metrics: Arc<MetricsCollection>,
167    ) -> CarbonResult<()> {
168        log::trace!(
169            "InstructionPipe::run(nested_instruction: {:?}, metrics)",
170            nested_instruction,
171        );
172
173        if let Some(decoded_instruction) = self
174            .decoder
175            .decode_instruction(&nested_instruction.instruction)
176        {
177            self.processor
178                .process(
179                    (
180                        nested_instruction.metadata.clone(),
181                        decoded_instruction,
182                        nested_instruction.inner_instructions.clone(),
183                    ),
184                    metrics.clone(),
185                )
186                .await?;
187        }
188
189        for nested_inner_instruction in nested_instruction.inner_instructions.iter() {
190            self.run(nested_inner_instruction, metrics.clone()).await?;
191        }
192
193        Ok(())
194    }
195}
196
197/// Represents a nested instruction with metadata, including potential inner
198/// instructions.
199///
200/// The `NestedInstruction` struct allows for recursive instruction handling,
201/// where each instruction may have associated metadata and a list of nested
202/// instructions.
203///
204/// # Fields
205///
206/// - `metadata`: The metadata associated with the instruction.
207/// - `instruction`: The Solana instruction being processed.
208/// - `inner_instructions`: A vector of `NestedInstruction`, representing any
209///   nested instructions.
210#[derive(Debug, Clone)]
211pub struct NestedInstruction {
212    pub metadata: InstructionMetadata,
213    pub instruction: solana_instruction::Instruction,
214    pub inner_instructions: NestedInstructions,
215}
216
217#[derive(Debug, Default)]
218pub struct NestedInstructions(pub Vec<NestedInstruction>);
219
220impl NestedInstructions {
221    pub fn iter(&self) -> std::slice::Iter<NestedInstruction> {
222        self.0.iter()
223    }
224
225    pub fn len(&self) -> usize {
226        self.0.len()
227    }
228
229    pub fn is_empty(&self) -> bool {
230        self.len() == 0
231    }
232
233    pub fn push(&mut self, nested_instruction: NestedInstruction) {
234        self.0.push(nested_instruction);
235    }
236}
237
238impl Deref for NestedInstructions {
239    type Target = [NestedInstruction];
240
241    fn deref(&self) -> &[NestedInstruction] {
242        &self.0[..]
243    }
244}
245
246impl DerefMut for NestedInstructions {
247    fn deref_mut(&mut self) -> &mut [NestedInstruction] {
248        &mut self.0[..]
249    }
250}
251
252impl Clone for NestedInstructions {
253    fn clone(&self) -> Self {
254        NestedInstructions(self.0.clone())
255    }
256}
257/// Nests instructions based on stack height, producing a hierarchy of
258/// `NestedInstruction`.
259///
260/// This function organizes instructions into a nested structure, enabling
261/// hierarchical transaction analysis. Instructions are nested according to
262/// their stack height, forming a tree-like structure.
263///
264/// # Parameters
265///
266/// - `instructions`: A list of tuples containing `InstructionMetadata` and
267///   instructions.
268///
269/// # Returns
270///
271/// A vector of `NestedInstruction`, representing the instructions organized by
272/// stack depth.
273impl From<InstructionsWithMetadata> for NestedInstructions {
274    fn from(instructions: InstructionsWithMetadata) -> Self {
275        log::trace!("from(instructions: {:?})", instructions);
276        let mut nested_ixs = NestedInstructions::default();
277
278        for (metadata, instruction) in instructions {
279            let nested_instruction = NestedInstruction {
280                metadata: metadata.clone(),
281                instruction,
282                inner_instructions: NestedInstructions::default(),
283            };
284
285            // compose root level of ixs
286            if metadata.stack_height == 1 || metadata.index == 0 {
287                nested_ixs.push(nested_instruction);
288                continue;
289            }
290            nested_ixs[metadata.index as usize]
291                .inner_instructions
292                .push(nested_instruction);
293        }
294
295        nested_ixs
296    }
297}
298#[cfg(test)]
299mod tests {
300
301    use solana_instruction::Instruction;
302
303    use super::*;
304
305    fn create_instruction_with_metadata(
306        index: u32,
307        stack_height: u32,
308    ) -> (InstructionMetadata, Instruction) {
309        let metadata = InstructionMetadata {
310            transaction_metadata: TransactionMetadata::default(),
311            stack_height,
312            index,
313        };
314        let instruction = Instruction {
315            program_id: Pubkey::new_unique(),
316            accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
317            data: vec![],
318        };
319        (metadata, instruction)
320    }
321
322    #[test]
323    fn test_nested_instructions_single_level() {
324        let instructions = vec![
325            create_instruction_with_metadata(1, 1),
326            create_instruction_with_metadata(2, 1),
327        ];
328        let nested_instructions: NestedInstructions = instructions.into();
329        assert_eq!(nested_instructions.len(), 2);
330        assert!(nested_instructions[0].inner_instructions.is_empty());
331        assert!(nested_instructions[1].inner_instructions.is_empty());
332    }
333
334    #[test]
335    fn test_nested_instructions_empty() {
336        let instructions: InstructionsWithMetadata = vec![];
337        let nested_instructions: NestedInstructions = instructions.into();
338        assert!(nested_instructions.is_empty());
339    }
340
341    #[test]
342    fn test_deep_nested_instructions() {
343        let instructions = vec![
344            create_instruction_with_metadata(0, 1),
345            create_instruction_with_metadata(0, 1),
346            create_instruction_with_metadata(1, 2),
347            create_instruction_with_metadata(1, 3),
348            create_instruction_with_metadata(1, 3),
349            create_instruction_with_metadata(1, 3),
350        ];
351
352        let nested_instructions: NestedInstructions = instructions.into();
353        assert_eq!(nested_instructions.len(), 2);
354        assert_eq!(nested_instructions.0[1].inner_instructions.len(), 4);
355    }
356}