atlas_arch/
instruction.rs

1use {
2    crate::{
3        error::IndexerResult, filter::Filter, metrics::MetricsCollection, processor::Processor,
4        transaction::TransactionMetadata,
5    },
6    arch_program::{account::AccountMeta, instruction::Instruction, pubkey::Pubkey},
7    async_trait::async_trait,
8    serde::{Deserialize, Serialize},
9    std::{
10        ops::{Deref, DerefMut},
11        sync::Arc,
12    },
13};
14
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct DecodedInstruction<T> {
17    pub program_id: Pubkey,
18    pub data: T,
19    pub accounts: Vec<AccountMeta>,
20}
21
22/// Decodes a raw `Instruction` into a typed representation.
23///
24/// Implement this for each program you wish to support. A decoder should
25/// return `Some(DecodedInstruction<..>)` only when it recognizes the
26/// instruction format for its program, otherwise return `None`.
27pub trait InstructionDecoder<'a> {
28    type InstructionType;
29
30    fn decode_instruction(
31        &self,
32        instruction: &'a Instruction,
33    ) -> Option<DecodedInstruction<Self::InstructionType>>;
34}
35
36pub type InstructionProcessorInputType<T> = (DecodedInstruction<T>, NestedInstruction);
37
38pub struct InstructionPipe<T: Send> {
39    pub decoder:
40        Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
41    pub processor: Box<
42        dyn Processor<InputType = InstructionProcessorInputType<T>, OutputType = ()>
43            + Send
44            + Sync
45            + 'static,
46    >,
47    pub filters: Vec<Box<dyn Filter + Send + Sync + 'static>>,
48}
49
50#[async_trait]
51/// Drives a set of instruction decoders and forwards decoded items to a
52/// processor.
53///
54/// Implementors should apply any configured `Filter`s before decoding or
55/// processing, and must be safe to call concurrently.
56pub trait InstructionPipes<'a>: Send + Sync {
57    async fn run(
58        &mut self,
59        instruction: &Vec<NestedInstruction>,
60        metrics: Arc<MetricsCollection>,
61    ) -> IndexerResult<()>;
62    fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>>;
63}
64
65#[async_trait]
66impl<T: Send + 'static> InstructionPipes<'_> for InstructionPipe<T> {
67    async fn run(
68        &mut self,
69        nested_instructions: &Vec<NestedInstruction>,
70        metrics: Arc<MetricsCollection>,
71    ) -> IndexerResult<()> {
72        log::trace!(
73            "InstructionPipe::run(instructions: {:?}, metrics)",
74            nested_instructions,
75        );
76
77        let mut decoded_instructions: Vec<InstructionProcessorInputType<T>> = Vec::new();
78        for nested_instruction in nested_instructions {
79            if let Some(decoded_instruction) = self
80                .decoder
81                .decode_instruction(&nested_instruction.instruction)
82            {
83                decoded_instructions.push((decoded_instruction, nested_instruction.clone()));
84            }
85
86            for nested_inner_instruction in nested_instruction.inner_instructions.iter() {
87                if let Some(decoded_instruction) = self
88                    .decoder
89                    .decode_instruction(&nested_inner_instruction.instruction)
90                {
91                    decoded_instructions
92                        .push((decoded_instruction, nested_inner_instruction.clone()));
93                }
94            }
95        }
96
97        self.processor
98            .process(decoded_instructions, metrics.clone())
99            .await?;
100
101        Ok(())
102    }
103
104    fn filters(&self) -> &Vec<Box<dyn Filter + Send + Sync + 'static>> {
105        &self.filters
106    }
107}
108
109#[derive(Debug, Default)]
110pub struct Instructions(pub Vec<Instruction>);
111
112impl Instructions {
113    /// Number of contained instructions.
114    pub fn len(&self) -> usize {
115        self.0.len()
116    }
117
118    /// Whether there are no contained instructions.
119    pub fn is_empty(&self) -> bool {
120        self.len() == 0
121    }
122
123    /// Append a raw instruction.
124    pub fn push(&mut self, instruction: Instruction) {
125        self.0.push(instruction);
126    }
127}
128
129impl Deref for Instructions {
130    type Target = [Instruction];
131
132    fn deref(&self) -> &[Instruction] {
133        &self.0[..]
134    }
135}
136
137impl DerefMut for Instructions {
138    fn deref_mut(&mut self) -> &mut [Instruction] {
139        &mut self.0[..]
140    }
141}
142
143impl Clone for Instructions {
144    fn clone(&self) -> Self {
145        Instructions(self.0.clone())
146    }
147}
148
149impl IntoIterator for Instructions {
150    type Item = Instruction;
151    type IntoIter = std::vec::IntoIter<Instruction>;
152
153    fn into_iter(self) -> Self::IntoIter {
154        self.0.into_iter()
155    }
156}
157
158#[derive(Debug, Clone)]
159pub struct InstructionMetadata {
160    pub transaction_metadata: Arc<TransactionMetadata>,
161    pub stack_height: u32,
162    pub index: u32,
163    pub absolute_path: Vec<u8>,
164}
165
166pub type InstructionsWithMetadata = Vec<(InstructionMetadata, Instruction)>;
167
168/// Represents a nested instruction with metadata, including potential inner
169/// instructions.
170///
171/// The `NestedInstruction` struct allows for recursive instruction handling,
172/// where each instruction may have associated metadata and a list of nested
173/// instructions.
174///
175/// # Fields
176///
177/// - `metadata`: The metadata associated with the instruction.
178/// - `instruction`: The Solana instruction being processed.
179/// - `inner_instructions`: A vector of `NestedInstruction`, representing any
180///   nested instructions.
181#[derive(Debug, Clone)]
182pub struct NestedInstruction {
183    pub metadata: InstructionMetadata,
184    pub instruction: Instruction,
185    pub inner_instructions: NestedInstructions,
186}
187
188#[derive(Debug, Default)]
189pub struct NestedInstructions(pub Vec<NestedInstruction>);
190
191impl NestedInstructions {
192    pub fn len(&self) -> usize {
193        self.0.len()
194    }
195
196    pub fn is_empty(&self) -> bool {
197        self.len() == 0
198    }
199
200    pub fn push(&mut self, nested_instruction: NestedInstruction) {
201        self.0.push(nested_instruction);
202    }
203}
204
205impl Deref for NestedInstructions {
206    type Target = [NestedInstruction];
207
208    fn deref(&self) -> &[NestedInstruction] {
209        &self.0[..]
210    }
211}
212
213impl DerefMut for NestedInstructions {
214    fn deref_mut(&mut self) -> &mut [NestedInstruction] {
215        &mut self.0[..]
216    }
217}
218
219impl Clone for NestedInstructions {
220    fn clone(&self) -> Self {
221        NestedInstructions(self.0.clone())
222    }
223}
224
225impl IntoIterator for NestedInstructions {
226    type Item = NestedInstruction;
227    type IntoIter = std::vec::IntoIter<NestedInstruction>;
228
229    fn into_iter(self) -> Self::IntoIter {
230        self.0.into_iter()
231    }
232}
233
234/// Nests instructions based on stack height, producing a hierarchy of
235/// `NestedInstruction`.
236///
237/// This function organizes instructions into a nested structure, enabling
238/// hierarchical transaction analysis. Instructions are nested according to
239/// their stack height, forming a tree-like structure.
240///
241/// # Parameters
242///
243/// - `instructions`: A list of tuples containing `InstructionMetadata` and
244///   instructions.
245///
246/// # Returns
247///
248/// A vector of `NestedInstruction`, representing the instructions organized by
249/// stack depth.
250impl From<InstructionsWithMetadata> for NestedInstructions {
251    fn from(instructions: InstructionsWithMetadata) -> Self {
252        log::trace!("from(instructions: {:?})", instructions);
253
254        // To avoid reallocations that result in dangling pointers.
255        // Therefore the number of "push"s must be calculated to set the capacity
256        let estimated_capacity = instructions
257            .iter()
258            .filter(|(meta, _)| meta.stack_height == 1)
259            .count();
260
261        UnsafeNestedBuilder::new(estimated_capacity).build(instructions)
262    }
263}
264
265// https://github.com/anza-xyz/agave/blob/master/program-runtime/src/execution_budget.rs#L7
266pub const MAX_INSTRUCTION_STACK_DEPTH: usize = 5;
267
268pub struct UnsafeNestedBuilder {
269    nested_ixs: Vec<NestedInstruction>,
270    level_ptrs: [Option<*mut NestedInstruction>; MAX_INSTRUCTION_STACK_DEPTH],
271}
272
273impl UnsafeNestedBuilder {
274    /// ## SAFETY:
275    /// Make sure `capacity` is large enough to avoid capacity expansion caused
276    /// by `push`
277    pub fn new(capacity: usize) -> Self {
278        Self {
279            nested_ixs: Vec::with_capacity(capacity),
280            level_ptrs: [None; MAX_INSTRUCTION_STACK_DEPTH],
281        }
282    }
283
284    pub fn build(mut self, instructions: InstructionsWithMetadata) -> NestedInstructions {
285        for (metadata, instruction) in instructions {
286            let stack_height = metadata.stack_height as usize;
287
288            assert!(stack_height > 0);
289            assert!(stack_height <= MAX_INSTRUCTION_STACK_DEPTH);
290
291            for ptr in &mut self.level_ptrs[stack_height..] {
292                *ptr = None;
293            }
294
295            let new_instruction = NestedInstruction {
296                metadata,
297                instruction,
298                inner_instructions: NestedInstructions::default(),
299            };
300
301            // SAFETY:The following operation is safe.
302            // because:
303            // 1. All pointers come from pre-allocated Vec (no extension)
304            // 2. level_ptr does not guarantee any aliasing
305            // 3. Lifecycle is limited to the build() method
306            unsafe {
307                if stack_height == 1 {
308                    self.nested_ixs.push(new_instruction);
309                    let ptr = self.nested_ixs.last_mut().unwrap_unchecked() as *mut _;
310                    self.level_ptrs[0] = Some(ptr);
311                } else if let Some(parent_ptr) = self.level_ptrs[stack_height - 2] {
312                    (*parent_ptr).inner_instructions.push(new_instruction);
313                    let ptr = (*parent_ptr)
314                        .inner_instructions
315                        .last_mut()
316                        .unwrap_unchecked() as *mut _;
317                    self.level_ptrs[stack_height - 1] = Some(ptr);
318                }
319            }
320        }
321
322        NestedInstructions(self.nested_ixs)
323    }
324}
325
326#[cfg(test)]
327mod tests {
328
329    use {
330        super::*,
331        arch_program::sanitized::ArchMessage,
332        arch_sdk::{RollbackStatus, Status},
333        Instruction,
334    };
335
336    fn create_instruction_with_metadata(
337        index: u32,
338        stack_height: u32,
339    ) -> (InstructionMetadata, Instruction) {
340        let metadata = InstructionMetadata {
341            transaction_metadata: Arc::new(TransactionMetadata {
342                id: String::from(" "),
343                fee_payer: Pubkey::default(),
344                message: ArchMessage::default(),
345                status: Status::Processed,
346                log_messages: vec![],
347                rollback_status: RollbackStatus::NotRolledback,
348                block_height: 0,
349                bitcoin_txid: None,
350                bitcoin_tx: None,
351                inner_instructions_list: vec![],
352            }),
353            stack_height,
354            index,
355            absolute_path: vec![],
356        };
357        let instruction = Instruction {
358            program_id: Pubkey::new_unique(),
359            accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
360            data: vec![],
361        };
362        (metadata, instruction)
363    }
364
365    #[test]
366    fn test_nested_instructions_single_level() {
367        let instructions = vec![
368            create_instruction_with_metadata(1, 1),
369            create_instruction_with_metadata(2, 1),
370        ];
371        let nested_instructions: NestedInstructions = instructions.into();
372        assert_eq!(nested_instructions.len(), 2);
373        assert!(nested_instructions[0].inner_instructions.is_empty());
374        assert!(nested_instructions[1].inner_instructions.is_empty());
375    }
376
377    #[test]
378    fn test_nested_instructions_empty() {
379        let instructions: InstructionsWithMetadata = vec![];
380        let nested_instructions: NestedInstructions = instructions.into();
381        assert!(nested_instructions.is_empty());
382    }
383
384    #[test]
385    fn test_deep_nested_instructions() {
386        let instructions = vec![
387            create_instruction_with_metadata(0, 1),
388            create_instruction_with_metadata(0, 1),
389            create_instruction_with_metadata(1, 2),
390            create_instruction_with_metadata(1, 3),
391            create_instruction_with_metadata(1, 3),
392            create_instruction_with_metadata(1, 3),
393        ];
394
395        let nested_instructions: NestedInstructions = instructions.into();
396        assert_eq!(nested_instructions.len(), 2);
397        assert_eq!(nested_instructions.0[1].inner_instructions.len(), 1);
398    }
399}