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}