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, filter::Filter, metrics::MetricsCollection, processor::Processor,
25 transaction::TransactionMetadata,
26 },
27 async_trait::async_trait,
28 serde::{Deserialize, Serialize},
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
52/// instruction indexes are grouped into one vector, so different inner
53/// instructions that have different stack heights may have continuous
54/// indexes.
55
56#[derive(Debug, Clone)]
57pub struct InstructionMetadata {
58 pub transaction_metadata: Arc<TransactionMetadata>,
59 pub stack_height: u32,
60 pub index: u32,
61 pub absolute_path: Vec<u8>,
62}
63
64pub type InstructionsWithMetadata = Vec<(InstructionMetadata, solana_instruction::Instruction)>;
65
66/// A decoded instruction containing program ID, data, and associated accounts.
67///
68/// The `DecodedInstruction` struct represents the outcome of decoding a raw
69/// instruction, encapsulating its program ID, parsed data, and the accounts
70/// involved.
71///
72/// # Type Parameters
73///
74/// - `T`: The type representing the decoded data for the instruction.
75///
76/// # Fields
77///
78/// - `program_id`: The program ID that owns the instruction.
79/// - `data`: The decoded data payload for the instruction, of type `T`.
80/// - `accounts`: A vector of `AccountMeta`, representing the accounts involved
81/// in the instruction.
82
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct DecodedInstruction<T> {
85 pub program_id: Pubkey,
86 pub data: T,
87 pub accounts: Vec<AccountMeta>,
88}
89
90/// A trait for decoding Solana instructions into a structured type.
91///
92/// Implement the `InstructionDecoder` trait for types that can decode raw
93/// instructions into a more meaningful structure, providing
94/// application-specific logic.
95///
96/// # Type Parameters
97///
98/// - `InstructionType`: The type into which the instruction data will be
99/// decoded.
100///
101/// # Required Methods
102///
103/// - `decode_instruction`: Decodes a raw Solana `Instruction` into a
104/// `DecodedInstruction`.
105pub trait InstructionDecoder<'a> {
106 type InstructionType;
107
108 fn decode_instruction(
109 &self,
110 instruction: &'a solana_instruction::Instruction,
111 ) -> Option<DecodedInstruction<Self::InstructionType>>;
112}
113
114/// The input type for the instruction processor.
115///
116/// - `T`: The instruction type
117pub type InstructionProcessorInputType<T> = (
118 InstructionMetadata,
119 DecodedInstruction<T>,
120 NestedInstructions,
121 solana_instruction::Instruction,
122);
123
124/// A processing pipeline for instructions, using a decoder and processor.
125///
126/// The `InstructionPipe` structure enables the processing of decoded
127/// instructions, pairing an `InstructionDecoder` with a `Processor`. It
128/// supports generic instruction types.
129///
130/// # Type Parameters
131///
132/// - `T`: The type representing the decoded instruction data.
133///
134/// # Fields
135///
136/// - `decoder`: The decoder used for parsing instructions.
137/// - `processor`: The processor that handles decoded instructions.
138/// - `filters`: A collection of filters that determine which instruction
139/// updates should be processed. Each filter in this collection is applied to
140/// incoming instruction updates, and only updates that pass all filters
141/// (return `true`) will be processed. If this collection is empty, all
142/// updates are processed.
143pub struct InstructionPipe<T: Send> {
144 pub decoder:
145 Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
146 pub processor:
147 Box<dyn Processor<InputType = InstructionProcessorInputType<T>> + Send + Sync + 'static>,
148 pub filters: Vec<Box<dyn Filter + Send + Sync + 'static>>,
149}
150
151/// An async trait for processing instructions within nested contexts.
152///
153/// The `InstructionPipes` trait allows for recursive processing of instructions
154/// that may contain nested instructions. This enables complex, hierarchical
155/// instruction handling for transactions.
156///
157/// # Required Methods
158///
159/// - `run`: Processes a `NestedInstruction`, recursively processing any inner
160/// instructions.
161/// - `filters`: Returns a reference to the filters associated with this pipe,
162/// which are used by the pipeline to determine which instruction updates
163/// should be processed.
164#[async_trait]
165pub trait InstructionPipes<'a>: Send + Sync {
166 async fn run(
167 &mut self,
168 nested_instruction: &NestedInstruction,
169 metrics: Arc<MetricsCollection>,
170 ) -> CarbonResult<()>;
171 fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>>;
172}
173
174#[async_trait]
175impl<T: Send + 'static> InstructionPipes<'_> for InstructionPipe<T> {
176 async fn run(
177 &mut self,
178 nested_instruction: &NestedInstruction,
179 metrics: Arc<MetricsCollection>,
180 ) -> CarbonResult<()> {
181 log::trace!(
182 "InstructionPipe::run(nested_instruction: {:?}, metrics)",
183 nested_instruction,
184 );
185
186 if let Some(decoded_instruction) = self
187 .decoder
188 .decode_instruction(&nested_instruction.instruction)
189 {
190 self.processor
191 .process(
192 (
193 nested_instruction.metadata.clone(),
194 decoded_instruction,
195 nested_instruction.inner_instructions.clone(),
196 nested_instruction.instruction.clone(),
197 ),
198 metrics.clone(),
199 )
200 .await?;
201 }
202
203 for nested_inner_instruction in nested_instruction.inner_instructions.iter() {
204 self.run(nested_inner_instruction, metrics.clone()).await?;
205 }
206
207 Ok(())
208 }
209
210 fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>> {
211 &self.filters
212 }
213}
214
215/// Represents a nested instruction with metadata, including potential inner
216/// instructions.
217///
218/// The `NestedInstruction` struct allows for recursive instruction handling,
219/// where each instruction may have associated metadata and a list of nested
220/// instructions.
221///
222/// # Fields
223///
224/// - `metadata`: The metadata associated with the instruction.
225/// - `instruction`: The Solana instruction being processed.
226/// - `inner_instructions`: A vector of `NestedInstruction`, representing any
227/// nested instructions.
228#[derive(Debug, Clone)]
229pub struct NestedInstruction {
230 pub metadata: InstructionMetadata,
231 pub instruction: solana_instruction::Instruction,
232 pub inner_instructions: NestedInstructions,
233}
234
235#[derive(Debug, Default)]
236pub struct NestedInstructions(pub Vec<NestedInstruction>);
237
238impl NestedInstructions {
239 pub fn len(&self) -> usize {
240 self.0.len()
241 }
242
243 pub fn is_empty(&self) -> bool {
244 self.len() == 0
245 }
246
247 pub fn push(&mut self, nested_instruction: NestedInstruction) {
248 self.0.push(nested_instruction);
249 }
250}
251
252impl Deref for NestedInstructions {
253 type Target = [NestedInstruction];
254
255 fn deref(&self) -> &[NestedInstruction] {
256 &self.0[..]
257 }
258}
259
260impl DerefMut for NestedInstructions {
261 fn deref_mut(&mut self) -> &mut [NestedInstruction] {
262 &mut self.0[..]
263 }
264}
265
266impl Clone for NestedInstructions {
267 fn clone(&self) -> Self {
268 NestedInstructions(self.0.clone())
269 }
270}
271
272impl IntoIterator for NestedInstructions {
273 type Item = NestedInstruction;
274 type IntoIter = std::vec::IntoIter<NestedInstruction>;
275
276 fn into_iter(self) -> Self::IntoIter {
277 self.0.into_iter()
278 }
279}
280
281/// Nests instructions based on stack height, producing a hierarchy of
282/// `NestedInstruction`.
283///
284/// This function organizes instructions into a nested structure, enabling
285/// hierarchical transaction analysis. Instructions are nested according to
286/// their stack height, forming a tree-like structure.
287///
288/// # Parameters
289///
290/// - `instructions`: A list of tuples containing `InstructionMetadata` and
291/// instructions.
292///
293/// # Returns
294///
295/// A vector of `NestedInstruction`, representing the instructions organized by
296/// stack depth.
297impl From<InstructionsWithMetadata> for NestedInstructions {
298 fn from(instructions: InstructionsWithMetadata) -> Self {
299 log::trace!("from(instructions: {:?})", instructions);
300
301 // To avoid reallocations that result in dangling pointers.
302 // Therefore the number of "push"s must be calculated to set the capacity
303 let estimated_capacity = instructions
304 .iter()
305 .filter(|(meta, _)| meta.stack_height == 1)
306 .count();
307
308 UnsafeNestedBuilder::new(estimated_capacity).build(instructions)
309 }
310}
311
312// https://github.com/anza-xyz/agave/blob/master/program-runtime/src/execution_budget.rs#L7
313pub const MAX_INSTRUCTION_STACK_DEPTH: usize = 5;
314
315pub struct UnsafeNestedBuilder {
316 nested_ixs: Vec<NestedInstruction>,
317 level_ptrs: [Option<*mut NestedInstruction>; MAX_INSTRUCTION_STACK_DEPTH],
318}
319
320impl UnsafeNestedBuilder {
321 /// ## SAFETY:
322 /// Make sure `capacity` is large enough to avoid capacity expansion caused
323 /// by `push`
324 pub fn new(capacity: usize) -> Self {
325 Self {
326 nested_ixs: Vec::with_capacity(capacity),
327 level_ptrs: [None; MAX_INSTRUCTION_STACK_DEPTH],
328 }
329 }
330
331 pub fn build(mut self, instructions: InstructionsWithMetadata) -> NestedInstructions {
332 for (metadata, instruction) in instructions {
333 let stack_height = metadata.stack_height as usize;
334
335 assert!(stack_height > 0);
336 assert!(stack_height <= MAX_INSTRUCTION_STACK_DEPTH);
337
338 for ptr in &mut self.level_ptrs[stack_height..] {
339 *ptr = None;
340 }
341
342 let new_instruction = NestedInstruction {
343 metadata,
344 instruction,
345 inner_instructions: NestedInstructions::default(),
346 };
347
348 // SAFETY:The following operation is safe.
349 // because:
350 // 1. All pointers come from pre-allocated Vec (no extension)
351 // 2. level_ptr does not guarantee any aliasing
352 // 3. Lifecycle is limited to the build() method
353 unsafe {
354 if stack_height == 1 {
355 self.nested_ixs.push(new_instruction);
356 let ptr = self.nested_ixs.last_mut().unwrap_unchecked() as *mut _;
357 self.level_ptrs[0] = Some(ptr);
358 } else if let Some(parent_ptr) = self.level_ptrs[stack_height - 2] {
359 (*parent_ptr).inner_instructions.push(new_instruction);
360 let ptr = (*parent_ptr)
361 .inner_instructions
362 .last_mut()
363 .unwrap_unchecked() as *mut _;
364 self.level_ptrs[stack_height - 1] = Some(ptr);
365 }
366 }
367 }
368
369 NestedInstructions(self.nested_ixs)
370 }
371}
372
373#[cfg(test)]
374mod tests {
375
376 use {super::*, solana_instruction::Instruction};
377
378 fn create_instruction_with_metadata(
379 index: u32,
380 stack_height: u32,
381 ) -> (InstructionMetadata, Instruction) {
382 let metadata = InstructionMetadata {
383 transaction_metadata: Arc::default(),
384 stack_height,
385 index,
386 absolute_path: vec![],
387 };
388 let instruction = Instruction {
389 program_id: Pubkey::new_unique(),
390 accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
391 data: vec![],
392 };
393 (metadata, instruction)
394 }
395
396 #[test]
397 fn test_nested_instructions_single_level() {
398 let instructions = vec![
399 create_instruction_with_metadata(1, 1),
400 create_instruction_with_metadata(2, 1),
401 ];
402 let nested_instructions: NestedInstructions = instructions.into();
403 assert_eq!(nested_instructions.len(), 2);
404 assert!(nested_instructions[0].inner_instructions.is_empty());
405 assert!(nested_instructions[1].inner_instructions.is_empty());
406 }
407
408 #[test]
409 fn test_nested_instructions_empty() {
410 let instructions: InstructionsWithMetadata = vec![];
411 let nested_instructions: NestedInstructions = instructions.into();
412 assert!(nested_instructions.is_empty());
413 }
414
415 #[test]
416 fn test_deep_nested_instructions() {
417 let instructions = vec![
418 create_instruction_with_metadata(0, 1),
419 create_instruction_with_metadata(0, 1),
420 create_instruction_with_metadata(1, 2),
421 create_instruction_with_metadata(1, 3),
422 create_instruction_with_metadata(1, 3),
423 create_instruction_with_metadata(1, 3),
424 ];
425
426 let nested_instructions: NestedInstructions = instructions.into();
427 assert_eq!(nested_instructions.len(), 2);
428 assert_eq!(nested_instructions.0[1].inner_instructions.len(), 1);
429 }
430}