1use anchor_lang::{
2 AnchorDeserialize, Discriminator, prelude::Pubkey,
3 solana_program::instruction::CompiledInstruction,
4};
5use data_anchor_blober::{
6 BLOB_ACCOUNT_INSTRUCTION_IDX, BLOB_BLOBER_INSTRUCTION_IDX, instruction::InsertChunk,
7};
8use itertools::Itertools;
9use serde::{Deserialize, Serialize};
10use solana_transaction::versioned::VersionedTransaction;
11use solana_transaction_status::InnerInstructions;
12
13use crate::PubkeyFromStr;
14
15#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
17pub struct BloberWithNamespace {
18 pub address: PubkeyFromStr,
20 pub namespace: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct VersionedTransactionWithInnerInstructions {
26 pub transaction: VersionedTransaction,
27 pub inner_instructions: Vec<InnerInstructions>,
28}
29
30impl From<VersionedTransaction> for VersionedTransactionWithInnerInstructions {
31 fn from(transaction: VersionedTransaction) -> Self {
32 Self {
33 transaction,
34 inner_instructions: Vec::new(),
35 }
36 }
37}
38
39impl From<&VersionedTransaction> for VersionedTransactionWithInnerInstructions {
40 fn from(transaction: &VersionedTransaction) -> Self {
41 Self {
42 transaction: transaction.clone(),
43 inner_instructions: Vec::new(),
44 }
45 }
46}
47
48impl VersionedTransactionWithInnerInstructions {
49 pub fn iter_instructions(&self) -> impl Iterator<Item = &CompiledInstruction> {
52 self.transaction.message.instructions().iter().chain(
53 self.inner_instructions
54 .iter()
55 .flat_map(|inner| inner.instructions.iter().map(|inner| &inner.instruction)),
56 )
57 }
58}
59
60pub enum RelevantInstruction {
62 DeclareBlob(data_anchor_blober::instruction::DeclareBlob),
63 InsertChunk(data_anchor_blober::instruction::InsertChunk),
64 FinalizeBlob(data_anchor_blober::instruction::FinalizeBlob),
65}
66
67impl std::fmt::Debug for RelevantInstruction {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 RelevantInstruction::DeclareBlob(instruction) => f
71 .debug_struct("DeclareBlob")
72 .field("size", &instruction.blob_size)
73 .field("timestamp", &instruction.timestamp)
74 .finish(),
75 RelevantInstruction::InsertChunk(instruction) => f
76 .debug_struct("InsertChunk")
77 .field("idx", &instruction.idx)
78 .finish(),
79 RelevantInstruction::FinalizeBlob(_) => f.debug_struct("FinalizeBlob").finish(),
80 }
81 }
82}
83
84impl Clone for RelevantInstruction {
85 fn clone(&self) -> Self {
86 match self {
87 RelevantInstruction::DeclareBlob(instruction) => {
88 RelevantInstruction::DeclareBlob(data_anchor_blober::instruction::DeclareBlob {
89 blob_size: instruction.blob_size,
90 timestamp: instruction.timestamp,
91 })
92 }
93 RelevantInstruction::InsertChunk(instruction) => {
94 RelevantInstruction::InsertChunk(data_anchor_blober::instruction::InsertChunk {
95 idx: instruction.idx,
96 data: instruction.data.clone(),
97 })
98 }
99 RelevantInstruction::FinalizeBlob(_) => {
100 RelevantInstruction::FinalizeBlob(data_anchor_blober::instruction::FinalizeBlob {})
101 }
102 }
103 }
104}
105
106impl RelevantInstruction {
107 pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
108 use data_anchor_blober::instruction::*;
109 let discriminator = compiled_instruction.data.get(..8)?;
110
111 match discriminator {
112 DeclareBlob::DISCRIMINATOR => {
113 let data = compiled_instruction.data.get(8..).unwrap_or_default();
114 DeclareBlob::try_from_slice(data)
115 .map(RelevantInstruction::DeclareBlob)
116 .ok()
117 }
118 InsertChunk::DISCRIMINATOR => {
119 let data = compiled_instruction.data.get(8..).unwrap_or_default();
120 InsertChunk::try_from_slice(data)
121 .map(RelevantInstruction::InsertChunk)
122 .ok()
123 }
124 FinalizeBlob::DISCRIMINATOR => {
125 let data = compiled_instruction.data.get(8..).unwrap_or_default();
126 FinalizeBlob::try_from_slice(data)
127 .map(RelevantInstruction::FinalizeBlob)
128 .ok()
129 }
130 _ => None,
134 }
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct RelevantInstructionWithAccounts {
141 pub blob: Pubkey,
142 pub blober: Pubkey,
143 pub instruction: RelevantInstruction,
144}
145
146pub fn deserialize_relevant_instructions(
149 program_id: &Pubkey,
150 tx: &VersionedTransactionWithInnerInstructions,
151 blob_pubkey_index: usize,
152 blober_pubkey_index: usize,
153) -> Vec<RelevantInstructionWithAccounts> {
154 tx.iter_instructions()
155 .filter_map(|compiled_instruction| {
156 let program_id_idx: usize = compiled_instruction.program_id_index.into();
157 let relevant_program_id = tx
158 .transaction
159 .message
160 .static_account_keys()
161 .get(program_id_idx)?;
162
163 if program_id != relevant_program_id {
164 return None; }
166
167 let blob =
168 get_account_at_index(&tx.transaction, compiled_instruction, blob_pubkey_index)?;
169 let blober =
170 get_account_at_index(&tx.transaction, compiled_instruction, blober_pubkey_index)?;
171 let instruction = RelevantInstruction::try_from_slice(compiled_instruction)?;
172 let relevant_instruction = RelevantInstructionWithAccounts {
173 blob,
174 blober,
175 instruction,
176 };
177
178 Some(relevant_instruction)
179 })
180 .collect()
181}
182
183pub enum RelevantBloberInstruction {
185 Initialize(data_anchor_blober::instruction::Initialize),
186 Close(data_anchor_blober::instruction::Close),
187}
188
189impl std::fmt::Debug for RelevantBloberInstruction {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 match self {
192 RelevantBloberInstruction::Initialize(instruction) => f
193 .debug_struct("Initialize")
194 .field("trusted", &instruction.trusted)
195 .finish(),
196 RelevantBloberInstruction::Close(_) => f.debug_struct("Close").finish(),
197 }
198 }
199}
200
201impl RelevantBloberInstruction {
202 pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
203 use data_anchor_blober::instruction::*;
204 let discriminator = compiled_instruction.data.get(..8)?;
205
206 match discriminator {
207 Initialize::DISCRIMINATOR => {
208 let data = compiled_instruction.data.get(8..).unwrap_or_default();
209 Initialize::try_from_slice(data)
210 .map(RelevantBloberInstruction::Initialize)
211 .ok()
212 }
213 Close::DISCRIMINATOR => {
214 let data = compiled_instruction.data.get(8..).unwrap_or_default();
215 Close::try_from_slice(data)
216 .map(RelevantBloberInstruction::Close)
217 .ok()
218 }
219 _ => None,
223 }
224 }
225}
226
227#[derive(Debug)]
229pub struct RelevantBloberInstructionWithPubkey {
230 pub blober: Pubkey,
231 pub instruction: RelevantBloberInstruction,
232}
233
234pub fn deserialize_blober_instructions(
236 program_id: &Pubkey,
237 tx: &VersionedTransactionWithInnerInstructions,
238) -> Vec<RelevantBloberInstructionWithPubkey> {
239 tx.iter_instructions()
240 .filter_map(|compiled_instruction| {
241 let program_id_idx: usize = compiled_instruction.program_id_index.into();
242
243 let relevant_program_id = tx
244 .transaction
245 .message
246 .static_account_keys()
247 .get(program_id_idx)?;
248
249 if program_id != relevant_program_id {
250 return None; }
252
253 let blober = get_account_at_index(&tx.transaction, compiled_instruction, 0)?;
254
255 let instruction = RelevantBloberInstruction::try_from_slice(compiled_instruction)?;
256
257 Some(RelevantBloberInstructionWithPubkey {
258 blober,
259 instruction,
260 })
261 })
262 .collect()
263}
264
265pub fn extract_relevant_instructions(
267 program_id: &Pubkey,
268 transactions: &[VersionedTransaction],
269) -> Vec<RelevantInstructionWithAccounts> {
270 transactions
271 .iter()
272 .flat_map(|tx| {
273 deserialize_relevant_instructions(
274 program_id,
275 &tx.into(),
276 BLOB_ACCOUNT_INSTRUCTION_IDX,
277 BLOB_BLOBER_INSTRUCTION_IDX,
278 )
279 })
280 .collect()
281}
282
283pub fn get_account_at_index(
287 tx: &VersionedTransaction,
288 instruction: &CompiledInstruction,
289 index: usize,
290) -> Option<Pubkey> {
291 let actual_index = *instruction.accounts.get(index)? as usize;
292 tx.message.static_account_keys().get(actual_index).copied()
293}
294
295#[derive(Debug, thiserror::Error)]
297pub enum LedgerDataBlobError {
298 #[error("No declare blob instruction found")]
300 DeclareNotFound,
301 #[error("Multiple declare instructions found")]
303 MultipleDeclares,
304 #[error("Declare blob size and inserts blob size mismatch")]
306 SizeMismatch,
307 #[error("No finalize instruction found")]
309 FinalizeNotFound,
310 #[error("Multiple finalize instructions found")]
312 MultipleFinalizes,
313 #[error("Blob account not owned by the program")]
315 AccountNotOwnedByProgram,
316 #[error("Invalid checkpoint account")]
318 InvalidCheckpointAccount(String),
319 #[error("Invalid blober account")]
320 InvalidBloberAccount(String),
321}
322
323pub fn get_blob_data_from_instructions(
325 relevant_instructions: &[RelevantInstructionWithAccounts],
326 blober: Pubkey,
327 blob: Pubkey,
328) -> Result<Vec<u8>, LedgerDataBlobError> {
329 let blob_size = relevant_instructions
330 .iter()
331 .filter_map(|instruction| {
332 if instruction.blober != blober || instruction.blob != blob {
333 return None;
334 }
335
336 match &instruction.instruction {
337 RelevantInstruction::DeclareBlob(declare) => Some(declare.blob_size),
338 _ => None,
339 }
340 })
341 .next()
342 .ok_or(LedgerDataBlobError::DeclareNotFound)?;
343
344 let inserts = relevant_instructions
345 .iter()
346 .filter_map(|instruction| {
347 if instruction.blober != blober || instruction.blob != blob {
348 return None;
349 }
350
351 let RelevantInstruction::InsertChunk(insert) = &instruction.instruction else {
352 return None;
353 };
354
355 Some(InsertChunk {
356 idx: insert.idx,
357 data: insert.data.clone(),
358 })
359 })
360 .collect::<Vec<InsertChunk>>();
361
362 let blob_data =
363 inserts
364 .iter()
365 .sorted_by_key(|insert| insert.idx)
366 .fold(Vec::new(), |mut acc, insert| {
367 acc.extend_from_slice(&insert.data);
368 acc
369 });
370
371 if blob_data.len() != blob_size as usize {
372 return Err(LedgerDataBlobError::SizeMismatch);
373 }
374
375 if !relevant_instructions.iter().any(|instruction| {
376 instruction.blober == blober
377 && instruction.blob == blob
378 && matches!(
379 instruction.instruction,
380 RelevantInstruction::FinalizeBlob(_)
381 )
382 }) {
383 return Err(LedgerDataBlobError::FinalizeNotFound);
384 }
385
386 Ok(blob_data)
387}