nitro_da_indexer_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 jsonrpsee::{
8    core::{RpcResult, SubscriptionResult},
9    proc_macros::rpc,
10};
11use nitro_da_proofs::compound::{
12    completeness::CompoundCompletenessProof, inclusion::CompoundInclusionProof,
13};
14use serde::{Deserialize, Serialize};
15use solana_sdk::{
16    clock::Slot, instruction::CompiledInstruction, pubkey::Pubkey,
17    transaction::VersionedTransaction,
18};
19
20/// A compound proof that proves whether a blob has been published in a specific slot.
21/// See [`CompoundInclusionProof`] and [`CompoundCompletenessProof`] for more information.
22#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
23pub enum CompoundProof {
24    /// See [`CompoundInclusionProof`].
25    Inclusion(CompoundInclusionProof),
26    /// See [`CompoundCompletenessProof`].
27    Completeness(CompoundCompletenessProof),
28}
29
30/// A data structure representing a blober's information, including the blober's pubkey, the
31/// payer's pubkey, and the network of the blober.
32#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct BloberData {
34    pub blober: Pubkey,
35    pub payer: Pubkey,
36    pub network_id: u64,
37}
38
39/// A time range with optional start and end times, used for filtering time.
40#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub struct TimeRange {
42    /// The start time of the range, inclusive.
43    pub start: Option<DateTime<Utc>>,
44    /// The end time of the range, inclusive.
45    pub end: Option<DateTime<Utc>>,
46}
47
48/// Request parameters for retrieving blobs by a specific blober's pubkey and a time range.
49#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub struct BlobsByBlober {
51    /// The blober's pubkey.
52    pub blober: Pubkey,
53    /// The time range for which to retrieve blobs.
54    #[serde(flatten)]
55    pub time_range: TimeRange,
56}
57
58/// Request parameters for retrieving blobs by a specific payer's pubkey, network ID, and a time range.
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub struct BlobsByPayer {
61    /// The payer's pubkey.
62    pub payer: Pubkey,
63    /// The network name of the blobs.
64    pub network_name: String,
65    /// The time range for which to retrieve blobs.
66    #[serde(flatten)]
67    pub time_range: TimeRange,
68}
69
70/// The Indexer RPC interface.
71#[rpc(server, client)]
72pub trait IndexerRpc {
73    /// Retrieve a list of blobs for a given slot and blober pubkey. Returns an error if there was a
74    /// database or RPC failure, and None if the slot has not been completed yet. If the slot is
75    /// completed, an empty list will be returned.
76    #[method(name = "get_blobs")]
77    async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
78
79    /// Retrieve a list of blobs for a given blober pubkey and time range. Returns an error if there
80    /// was a database or RPC failure, and an empty list if no blobs were found.
81    #[method(name = "get_blobs_by_blober")]
82    async fn get_blobs_by_blober(&self, blober: BlobsByBlober) -> RpcResult<Vec<Vec<u8>>>;
83
84    /// Retrieve a list of blobs for a given payer pubkey, network ID, and time range. Returns an
85    /// error if there was a database or RPC failure, and an empty list if no blobs were found.
86    #[method(name = "get_blobs_by_payer")]
87    async fn get_blobs_by_payer(&self, payer: BlobsByPayer) -> RpcResult<Vec<Vec<u8>>>;
88
89    /// Retrieve a proof for a given slot and blober pubkey. Returns an error if there was a
90    /// database or RPC failure, and None if the slot has not been completed yet.
91    #[method(name = "get_proof")]
92    async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
93
94    /// Retrieve a compound proof that covers a particular blob. Returns an error if there was a
95    /// database or RPC failure, and None if the blob does not exist.
96    #[method(name = "get_proof_for_blob")]
97    async fn get_proof_for_blob(&self, blob_address: Pubkey) -> RpcResult<Option<CompoundProof>>;
98
99    /// Add a list of blober PDA addresses to the list of tracked blobers.
100    #[method(name = "add_blobers")]
101    async fn add_blobers(&self, blobers: HashSet<BloberData>) -> RpcResult<()>;
102
103    /// Remove a list of blober PDA addresses from the list of tracked blobers.
104    #[method(name = "remove_blobers")]
105    async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
106
107    /// Listen to blob finalization events from specified blobers. This will return a stream of
108    /// slots and blober PDAs that have finalized blobs. The stream will be closed when the RPC server is
109    /// shut down.
110    #[subscription(name = "subscribe_blob_finalization" => "listen_subscribe_blob_finalization", unsubscribe = "unsubscribe_blob_finalization", item = (Pubkey, Slot))]
111    async fn subscribe_blob_finalization(&self, blobers: HashSet<Pubkey>) -> SubscriptionResult;
112}
113
114/// A relevant [`blober`] instruction extracted from a [`VersionedTransaction`].
115pub enum RelevantInstruction {
116    DeclareBlob(nitro_da_blober::instruction::DeclareBlob),
117    InsertChunk(nitro_da_blober::instruction::InsertChunk),
118    FinalizeBlob(nitro_da_blober::instruction::FinalizeBlob),
119}
120
121impl RelevantInstruction {
122    pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
123        use nitro_da_blober::instruction::*;
124        let discriminator = compiled_instruction.data.get(..8)?;
125
126        match discriminator {
127            DeclareBlob::DISCRIMINATOR => {
128                let data = compiled_instruction.data.get(8..).unwrap_or_default();
129                DeclareBlob::try_from_slice(data)
130                    .map(RelevantInstruction::DeclareBlob)
131                    .ok()
132            }
133            InsertChunk::DISCRIMINATOR => {
134                let data = compiled_instruction.data.get(8..).unwrap_or_default();
135                InsertChunk::try_from_slice(data)
136                    .map(RelevantInstruction::InsertChunk)
137                    .ok()
138            }
139            FinalizeBlob::DISCRIMINATOR => {
140                let data = compiled_instruction.data.get(8..).unwrap_or_default();
141                FinalizeBlob::try_from_slice(data)
142                    .map(RelevantInstruction::FinalizeBlob)
143                    .ok()
144            }
145            // If we don't recognize the discriminator, we ignore the instruction - there might be
146            // more instructions packed into the same transaction which might not be relevant to
147            // us.
148            _ => None,
149        }
150    }
151}
152
153/// A deserialized relevant instruction, containing the blob and blober pubkeys and the instruction.
154pub struct RelevantInstructionWithAccounts {
155    pub blob: Pubkey,
156    pub blober: Pubkey,
157    pub instruction: RelevantInstruction,
158}
159
160/// Deserialize relevant instructions from a transaction, given the indices of the blob and blober
161/// accounts in the transaction.
162pub fn deserialize_relevant_instructions(
163    tx: &VersionedTransaction,
164    blob_pubkey_index: usize,
165    blober_pubkey_index: usize,
166) -> Vec<RelevantInstructionWithAccounts> {
167    tx.message
168        .instructions()
169        .iter()
170        .filter_map(|compiled_instruction| {
171            Some(RelevantInstructionWithAccounts {
172                blob: get_account_at_index(tx, compiled_instruction, blob_pubkey_index)?,
173                blober: get_account_at_index(tx, compiled_instruction, blober_pubkey_index)?,
174                instruction: RelevantInstruction::try_from_slice(compiled_instruction)?,
175            })
176        })
177        .collect()
178}
179
180/// Extract relevant instructions from a list of transactions.
181pub fn extract_relevant_instructions(
182    transactions: &[VersionedTransaction],
183) -> Vec<RelevantInstructionWithAccounts> {
184    transactions
185        .iter()
186        .flat_map(|tx| deserialize_relevant_instructions(tx, 0, 1))
187        .collect()
188}
189
190/// Performs the double-lookup required to find an account at a given account index in an instruction.
191/// This is required because the accounts are not stored in the instruction directly, but in a separate
192/// account list. It is computed as `payload.account_keys[instruction.accounts[index]]`.
193pub fn get_account_at_index(
194    tx: &VersionedTransaction,
195    instruction: &CompiledInstruction,
196    index: usize,
197) -> Option<Pubkey> {
198    let actual_index = *instruction.accounts.get(index)? as usize;
199    tx.message.static_account_keys().get(actual_index).copied()
200}