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}