data_anchor_api/
lib.rs

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/// A compound proof that proves whether a blob has been published in a specific slot.
22/// See [`CompoundInclusionProof`] and [`CompoundCompletenessProof`] for more information.
23#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
24pub enum CompoundProof {
25    /// See [`CompoundInclusionProof`].
26    Inclusion(CompoundInclusionProof),
27    /// See [`CompoundCompletenessProof`].
28    Completeness(CompoundCompletenessProof),
29}
30
31/// A data structure representing a blober's information, including the blober's pubkey, the
32/// payer's pubkey, and the network of the blober.
33#[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/// A time range with optional start and end times, used for filtering time.
41#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub struct TimeRange {
43    /// The start time of the range, inclusive.
44    pub start: Option<DateTime<Utc>>,
45    /// The end time of the range, inclusive.
46    pub end: Option<DateTime<Utc>>,
47}
48
49/// Request parameters for retrieving blobs by a specific blober's pubkey and a time range.
50#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct BlobsByBlober {
52    /// The blober's pubkey.
53    pub blober: Pubkey,
54    /// The time range for which to retrieve blobs.
55    #[serde(flatten)]
56    pub time_range: TimeRange,
57}
58
59/// Request parameters for retrieving blobs by a specific payer's pubkey, network ID, and a time range.
60#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
61pub struct BlobsByPayer {
62    /// The payer's pubkey.
63    pub payer: Pubkey,
64    /// The network name of the blobs.
65    pub network_name: String,
66    /// The time range for which to retrieve blobs.
67    #[serde(flatten)]
68    pub time_range: TimeRange,
69}
70
71/// The Indexer RPC interface.
72#[rpc(server, client)]
73pub trait IndexerRpc {
74    /// Retrieve a list of blobs for a given slot and blober pubkey. Returns an error if there was a
75    /// database or RPC failure, and None if the slot has not been completed yet. If the slot is
76    /// completed, an empty list will be returned.
77    #[method(name = "get_blobs")]
78    async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
79
80    /// Retrieve a list of blobs for a given blober pubkey and time range. Returns an error if there
81    /// was a database or RPC failure, and an empty list if no blobs were found.
82    #[method(name = "get_blobs_by_blober")]
83    async fn get_blobs_by_blober(&self, blober: BlobsByBlober) -> RpcResult<Vec<Vec<u8>>>;
84
85    /// Retrieve a list of blobs for a given payer pubkey, network ID, and time range. Returns an
86    /// error if there was a database or RPC failure, and an empty list if no blobs were found.
87    #[method(name = "get_blobs_by_payer")]
88    async fn get_blobs_by_payer(&self, payer: BlobsByPayer) -> RpcResult<Vec<Vec<u8>>>;
89
90    /// Retrieve a proof for a given slot and blober pubkey. Returns an error if there was a
91    /// database or RPC failure, and None if the slot has not been completed yet.
92    #[method(name = "get_proof")]
93    async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
94
95    /// Retrieve a compound proof that covers a particular blob. Returns an error if there was a
96    /// database or RPC failure, and None if the blob does not exist.
97    #[method(name = "get_proof_for_blob")]
98    async fn get_proof_for_blob(&self, blob_address: Pubkey) -> RpcResult<Option<CompoundProof>>;
99
100    /// Add a list of blober PDA addresses to the list of tracked blobers.
101    #[method(name = "add_blobers")]
102    async fn add_blobers(&self, blobers: HashSet<BloberData>) -> RpcResult<()>;
103
104    /// Remove a list of blober PDA addresses from the list of tracked blobers.
105    #[method(name = "remove_blobers")]
106    async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
107
108    /// Listen to blob finalization events from specified blobers. This will return a stream of
109    /// slots and blober PDAs that have finalized blobs. The stream will be closed when the RPC server is
110    /// shut down.
111    #[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
115/// A relevant [`blober`] instruction extracted from a [`VersionedTransaction`].
116pub 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            // If we don't recognize the discriminator, we ignore the instruction - there might be
147            // more instructions packed into the same transaction which might not be relevant to
148            // us.
149            _ => None,
150        }
151    }
152}
153
154/// A deserialized relevant instruction, containing the blob and blober pubkeys and the instruction.
155pub 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
185/// Deserialize relevant instructions from a transaction, given the indices of the blob and blober
186/// accounts in the transaction.
187pub 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
220/// Extract relevant instructions from a list of transactions.
221pub 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
230/// Performs the double-lookup required to find an account at a given account index in an instruction.
231/// This is required because the accounts are not stored in the instruction directly, but in a separate
232/// account list. It is computed as `payload.account_keys[instruction.accounts[index]]`.
233pub 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}