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}