1#![doc = include_str!("../README.md")]
2
3use std::collections::HashSet;
4
5use anchor_lang::{AnchorDeserialize, Discriminator};
6use chrono::{DateTime, Utc};
7use data_anchor_proofs::compound::{
8 completeness::CompoundCompletenessProof, inclusion::CompoundInclusionProof,
9};
10use jsonrpsee::{
11 core::{RpcResult, SubscriptionResult},
12 proc_macros::rpc,
13};
14use serde::{Deserialize, Serialize};
15use solana_sdk::{
16 clock::Slot, instruction::CompiledInstruction, pubkey::Pubkey,
17 transaction::VersionedTransaction,
18};
19use solana_transaction_status::InnerInstructions;
20
21#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
24pub enum CompoundProof {
25 Inclusion(CompoundInclusionProof),
27 Completeness(CompoundCompletenessProof),
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct BloberData {
35 pub blober: Pubkey,
36 pub payer: Pubkey,
37 pub network_id: u64,
38}
39
40#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub struct TimeRange {
43 pub start: Option<DateTime<Utc>>,
45 pub end: Option<DateTime<Utc>>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct BlobsByBlober {
52 pub blober: Pubkey,
54 #[serde(flatten)]
56 pub time_range: TimeRange,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
61pub struct BlobsByPayer {
62 pub payer: Pubkey,
64 pub network_name: String,
66 #[serde(flatten)]
68 pub time_range: TimeRange,
69}
70
71#[rpc(server, client)]
73pub trait IndexerRpc {
74 #[method(name = "get_blobs")]
78 async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
79
80 #[method(name = "get_blobs_by_blober")]
83 async fn get_blobs_by_blober(&self, blober: BlobsByBlober) -> RpcResult<Vec<Vec<u8>>>;
84
85 #[method(name = "get_blobs_by_payer")]
88 async fn get_blobs_by_payer(&self, payer: BlobsByPayer) -> RpcResult<Vec<Vec<u8>>>;
89
90 #[method(name = "get_proof")]
93 async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
94
95 #[method(name = "get_proof_for_blob")]
98 async fn get_proof_for_blob(&self, blob_address: Pubkey) -> RpcResult<Option<CompoundProof>>;
99
100 #[method(name = "add_blobers")]
102 async fn add_blobers(&self, blobers: HashSet<BloberData>) -> RpcResult<()>;
103
104 #[method(name = "remove_blobers")]
106 async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
107
108 #[subscription(name = "subscribe_blob_finalization" => "listen_subscribe_blob_finalization", unsubscribe = "unsubscribe_blob_finalization", item = (Pubkey, Slot))]
112 async fn subscribe_blob_finalization(&self, blobers: HashSet<Pubkey>) -> SubscriptionResult;
113}
114
115pub enum RelevantInstruction {
117 DeclareBlob(data_anchor_blober::instruction::DeclareBlob),
118 InsertChunk(data_anchor_blober::instruction::InsertChunk),
119 FinalizeBlob(data_anchor_blober::instruction::FinalizeBlob),
120}
121
122impl RelevantInstruction {
123 pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
124 use data_anchor_blober::instruction::*;
125 let discriminator = compiled_instruction.data.get(..8)?;
126
127 match discriminator {
128 DeclareBlob::DISCRIMINATOR => {
129 let data = compiled_instruction.data.get(8..).unwrap_or_default();
130 DeclareBlob::try_from_slice(data)
131 .map(RelevantInstruction::DeclareBlob)
132 .ok()
133 }
134 InsertChunk::DISCRIMINATOR => {
135 let data = compiled_instruction.data.get(8..).unwrap_or_default();
136 InsertChunk::try_from_slice(data)
137 .map(RelevantInstruction::InsertChunk)
138 .ok()
139 }
140 FinalizeBlob::DISCRIMINATOR => {
141 let data = compiled_instruction.data.get(8..).unwrap_or_default();
142 FinalizeBlob::try_from_slice(data)
143 .map(RelevantInstruction::FinalizeBlob)
144 .ok()
145 }
146 _ => None,
150 }
151 }
152}
153
154pub struct RelevantInstructionWithAccounts {
156 pub blob: Pubkey,
157 pub blober: Pubkey,
158 pub instruction: RelevantInstruction,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct VersionedTransactionWithInnerInstructions {
163 pub transaction: VersionedTransaction,
164 pub inner_instructions: Option<Vec<InnerInstructions>>,
165}
166
167impl From<VersionedTransaction> for VersionedTransactionWithInnerInstructions {
168 fn from(transaction: VersionedTransaction) -> Self {
169 Self {
170 transaction,
171 inner_instructions: None,
172 }
173 }
174}
175
176impl From<&VersionedTransaction> for VersionedTransactionWithInnerInstructions {
177 fn from(transaction: &VersionedTransaction) -> Self {
178 Self {
179 transaction: transaction.clone(),
180 inner_instructions: None,
181 }
182 }
183}
184
185pub fn deserialize_relevant_instructions(
188 tx: &VersionedTransactionWithInnerInstructions,
189 blob_pubkey_index: usize,
190 blober_pubkey_index: usize,
191) -> Vec<RelevantInstructionWithAccounts> {
192 tx.transaction
193 .message
194 .instructions()
195 .iter()
196 .chain(tx.inner_instructions.iter().flat_map(|inner_instructions| {
197 inner_instructions
198 .iter()
199 .flat_map(|inner| &inner.instructions)
200 .map(|inner| &inner.instruction)
201 }))
202 .filter_map(|compiled_instruction| {
203 Some(RelevantInstructionWithAccounts {
204 blob: get_account_at_index(
205 &tx.transaction,
206 compiled_instruction,
207 blob_pubkey_index,
208 )?,
209 blober: get_account_at_index(
210 &tx.transaction,
211 compiled_instruction,
212 blober_pubkey_index,
213 )?,
214 instruction: RelevantInstruction::try_from_slice(compiled_instruction)?,
215 })
216 })
217 .collect()
218}
219
220pub fn extract_relevant_instructions(
222 transactions: &[VersionedTransaction],
223) -> Vec<RelevantInstructionWithAccounts> {
224 transactions
225 .iter()
226 .flat_map(|tx| deserialize_relevant_instructions(&tx.into(), 0, 1))
227 .collect()
228}
229
230pub fn get_account_at_index(
234 tx: &VersionedTransaction,
235 instruction: &CompiledInstruction,
236 index: usize,
237) -> Option<Pubkey> {
238 let actual_index = *instruction.accounts.get(index)? as usize;
239 tx.message.static_account_keys().get(actual_index).copied()
240}