data_anchor_api/
indexing.rs

1use anchor_lang::{AnchorDeserialize, Discriminator};
2use data_anchor_blober::{BLOB_ACCOUNT_INSTRUCTION_IDX, BLOB_BLOBER_INSTRUCTION_IDX};
3use serde::{Deserialize, Serialize};
4use solana_sdk::{
5    instruction::CompiledInstruction, pubkey::Pubkey, transaction::VersionedTransaction,
6};
7use solana_transaction_status::InnerInstructions;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct VersionedTransactionWithInnerInstructions {
11    pub transaction: VersionedTransaction,
12    pub inner_instructions: Vec<InnerInstructions>,
13}
14
15impl From<VersionedTransaction> for VersionedTransactionWithInnerInstructions {
16    fn from(transaction: VersionedTransaction) -> Self {
17        Self {
18            transaction,
19            inner_instructions: Vec::new(),
20        }
21    }
22}
23
24impl From<&VersionedTransaction> for VersionedTransactionWithInnerInstructions {
25    fn from(transaction: &VersionedTransaction) -> Self {
26        Self {
27            transaction: transaction.clone(),
28            inner_instructions: Vec::new(),
29        }
30    }
31}
32
33impl VersionedTransactionWithInnerInstructions {
34    /// Create an iterator over all instructions in the transaction, including both top-level and
35    /// inner instructions.
36    pub fn iter_instructions(&self) -> impl Iterator<Item = &CompiledInstruction> {
37        self.transaction.message.instructions().iter().chain(
38            self.inner_instructions
39                .iter()
40                .flat_map(|inner| inner.instructions.iter().map(|inner| &inner.instruction)),
41        )
42    }
43}
44
45/// A relevant [`data_anchor_blober`] instruction extracted from a [`VersionedTransaction`].
46pub enum RelevantInstruction {
47    DeclareBlob(data_anchor_blober::instruction::DeclareBlob),
48    InsertChunk(data_anchor_blober::instruction::InsertChunk),
49    FinalizeBlob(data_anchor_blober::instruction::FinalizeBlob),
50}
51
52impl std::fmt::Debug for RelevantInstruction {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            RelevantInstruction::DeclareBlob(instruction) => f
56                .debug_struct("DeclareBlob")
57                .field("size", &instruction.blob_size)
58                .field("timestamp", &instruction.timestamp)
59                .finish(),
60            RelevantInstruction::InsertChunk(instruction) => f
61                .debug_struct("InsertChunk")
62                .field("idx", &instruction.idx)
63                .finish(),
64            RelevantInstruction::FinalizeBlob(_) => f.debug_struct("FinalizeBlob").finish(),
65        }
66    }
67}
68
69impl RelevantInstruction {
70    pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
71        use data_anchor_blober::instruction::*;
72        let discriminator = compiled_instruction.data.get(..8)?;
73
74        match discriminator {
75            DeclareBlob::DISCRIMINATOR => {
76                let data = compiled_instruction.data.get(8..).unwrap_or_default();
77                DeclareBlob::try_from_slice(data)
78                    .map(RelevantInstruction::DeclareBlob)
79                    .ok()
80            }
81            InsertChunk::DISCRIMINATOR => {
82                let data = compiled_instruction.data.get(8..).unwrap_or_default();
83                InsertChunk::try_from_slice(data)
84                    .map(RelevantInstruction::InsertChunk)
85                    .ok()
86            }
87            FinalizeBlob::DISCRIMINATOR => {
88                let data = compiled_instruction.data.get(8..).unwrap_or_default();
89                FinalizeBlob::try_from_slice(data)
90                    .map(RelevantInstruction::FinalizeBlob)
91                    .ok()
92            }
93            // If we don't recognize the discriminator, we ignore the instruction - there might be
94            // more instructions packed into the same transaction which might not be relevant to
95            // us.
96            _ => None,
97        }
98    }
99}
100
101/// A deserialized relevant instruction, containing the blob and blober pubkeys and the instruction.
102#[derive(Debug)]
103pub struct RelevantInstructionWithAccounts {
104    pub blob: Pubkey,
105    pub blober: Pubkey,
106    pub instruction: RelevantInstruction,
107}
108
109/// Deserialize relevant instructions from a transaction, given the indices of the blob and blober
110/// accounts in the transaction.
111pub fn deserialize_relevant_instructions(
112    program_id: &Pubkey,
113    tx: &VersionedTransactionWithInnerInstructions,
114    blob_pubkey_index: usize,
115    blober_pubkey_index: usize,
116) -> Vec<RelevantInstructionWithAccounts> {
117    tx.iter_instructions()
118        .filter_map(|compiled_instruction| {
119            let program_id_idx: usize = compiled_instruction.program_id_index.into();
120            let relevant_program_id = tx
121                .transaction
122                .message
123                .static_account_keys()
124                .get(program_id_idx)?;
125
126            if program_id != relevant_program_id {
127                return None; // Skip instructions not related to the specified program ID.
128            }
129
130            let blob =
131                get_account_at_index(&tx.transaction, compiled_instruction, blob_pubkey_index)?;
132            let blober =
133                get_account_at_index(&tx.transaction, compiled_instruction, blober_pubkey_index)?;
134            let instruction = RelevantInstruction::try_from_slice(compiled_instruction)?;
135            let relevant_instruction = RelevantInstructionWithAccounts {
136                blob,
137                blober,
138                instruction,
139            };
140
141            Some(relevant_instruction)
142        })
143        .collect()
144}
145
146/// Blober instructions that are relevant to the indexer.
147pub enum RelevantBloberInstruction {
148    Initialize(data_anchor_blober::instruction::Initialize),
149    Close(data_anchor_blober::instruction::Close),
150}
151
152impl std::fmt::Debug for RelevantBloberInstruction {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match self {
155            RelevantBloberInstruction::Initialize(instruction) => f
156                .debug_struct("Initialize")
157                .field("trusted", &instruction.trusted)
158                .finish(),
159            RelevantBloberInstruction::Close(_) => f.debug_struct("Close").finish(),
160        }
161    }
162}
163
164impl RelevantBloberInstruction {
165    pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
166        use data_anchor_blober::instruction::*;
167        let discriminator = compiled_instruction.data.get(..8)?;
168
169        match discriminator {
170            Initialize::DISCRIMINATOR => {
171                let data = compiled_instruction.data.get(8..).unwrap_or_default();
172                Initialize::try_from_slice(data)
173                    .map(RelevantBloberInstruction::Initialize)
174                    .ok()
175            }
176            Close::DISCRIMINATOR => {
177                let data = compiled_instruction.data.get(8..).unwrap_or_default();
178                Close::try_from_slice(data)
179                    .map(RelevantBloberInstruction::Close)
180                    .ok()
181            }
182            // If we don't recognize the discriminator, we ignore the instruction - there might be
183            // more instructions packed into the same transaction which might not be relevant to
184            // us.
185            _ => None,
186        }
187    }
188}
189
190/// A deserialized relevant blober instruction, containing the blober pubkey and the instruction.
191#[derive(Debug)]
192pub struct RelevantBloberInstructionWithPubkey {
193    pub blober: Pubkey,
194    pub instruction: RelevantBloberInstruction,
195}
196
197/// Deserialize blober instructions from a transaction, returning a vector of [`RelevantBloberInstructionWithPubkey`].
198pub fn deserialize_blober_instructions(
199    program_id: &Pubkey,
200    tx: &VersionedTransactionWithInnerInstructions,
201) -> Vec<RelevantBloberInstructionWithPubkey> {
202    tx.iter_instructions()
203        .filter_map(|compiled_instruction| {
204            let program_id_idx: usize = compiled_instruction.program_id_index.into();
205
206            let relevant_program_id = tx
207                .transaction
208                .message
209                .static_account_keys()
210                .get(program_id_idx)?;
211
212            if program_id != relevant_program_id {
213                return None; // Skip instructions not related to the specified program ID.
214            }
215
216            let blober = get_account_at_index(&tx.transaction, compiled_instruction, 0)?;
217
218            let instruction = RelevantBloberInstruction::try_from_slice(compiled_instruction)?;
219
220            Some(RelevantBloberInstructionWithPubkey {
221                blober,
222                instruction,
223            })
224        })
225        .collect()
226}
227
228/// Extract relevant instructions from a list of transactions.
229pub fn extract_relevant_instructions(
230    program_id: &Pubkey,
231    transactions: &[VersionedTransaction],
232) -> Vec<RelevantInstructionWithAccounts> {
233    transactions
234        .iter()
235        .flat_map(|tx| {
236            deserialize_relevant_instructions(
237                program_id,
238                &tx.into(),
239                BLOB_ACCOUNT_INSTRUCTION_IDX,
240                BLOB_BLOBER_INSTRUCTION_IDX,
241            )
242        })
243        .collect()
244}
245
246/// Performs the double-lookup required to find an account at a given account index in an instruction.
247/// This is required because the accounts are not stored in the instruction directly, but in a separate
248/// account list. It is computed as `payload.account_keys[instruction.accounts[index]]`.
249pub fn get_account_at_index(
250    tx: &VersionedTransaction,
251    instruction: &CompiledInstruction,
252    index: usize,
253) -> Option<Pubkey> {
254    let actual_index = *instruction.accounts.get(index)? as usize;
255    tx.message.static_account_keys().get(actual_index).copied()
256}