nitro_da_indexer_api/
lib.rs

1use std::collections::HashSet;
2
3use anchor_lang::{AnchorDeserialize, Discriminator};
4use jsonrpsee::{
5    core::{RpcResult, SubscriptionResult},
6    proc_macros::rpc,
7};
8use nitro_da_proofs::compound::{
9    completeness::CompoundCompletenessProof, inclusion::CompoundInclusionProof,
10};
11use serde::{Deserialize, Serialize};
12use solana_sdk::{
13    clock::Slot, instruction::CompiledInstruction, pubkey::Pubkey,
14    transaction::VersionedTransaction,
15};
16
17/// A compound proof that proves whether a blob has been published in a specific slot.
18/// See [`CompoundInclusionProof`] and [`CompoundCompletenessProof`] for more information.
19#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
20pub enum CompoundProof {
21    /// See [`CompoundInclusionProof`].
22    Inclusion(CompoundInclusionProof),
23    /// See [`CompoundCompletenessProof`].
24    Completeness(CompoundCompletenessProof),
25}
26
27/// The Indexer RPC interface.
28#[rpc(server, client)]
29pub trait IndexerRpc {
30    /// Retrieve a list of blobs for a given slot and blober pubkey. Returns an error if there was a
31    /// database or RPC failure, and None if the slot has not been completed yet. If the slot is
32    /// completed, an empty list will be returned.
33    #[method(name = "get_blobs")]
34    async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
35
36    /// Retrieve a proof for a given slot and blober pubkey. Returns an error if there was a
37    /// database or RPC failure, and None if the slot has not been completed yet.
38    #[method(name = "get_proof")]
39    async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
40
41    /// Add a list of blober PDA addresses to the list of tracked blobers.
42    #[method(name = "add_blobers")]
43    async fn add_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
44
45    /// Remove a list of blober PDA addresses from the list of tracked blobers.
46    #[method(name = "remove_blobers")]
47    async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
48
49    /// Listen to blob finalization events from specified blobers. This will return a stream of
50    /// slots and blober PDAs that have finalized blobs. The stream will be closed when the RPC server is
51    /// shut down.
52    #[subscription(name = "subscribe_blob_finalization" => "listen_subscribe_blob_finalization", unsubscribe = "unsubscribe_blob_finalization", item = (Pubkey, Slot))]
53    async fn subscribe_blob_finalization(&self, blobers: HashSet<Pubkey>) -> SubscriptionResult;
54}
55
56/// A relevant [`blober`] instruction extracted from a [`VersionedTransaction`].
57pub enum RelevantInstruction {
58    DeclareBlob(nitro_da_blober::instruction::DeclareBlob),
59    InsertChunk(nitro_da_blober::instruction::InsertChunk),
60    FinalizeBlob(nitro_da_blober::instruction::FinalizeBlob),
61}
62
63impl RelevantInstruction {
64    pub fn try_from_slice(compiled_instruction: &CompiledInstruction) -> Option<Self> {
65        use nitro_da_blober::instruction::*;
66        let discriminator = compiled_instruction.data.get(..8)?;
67
68        match discriminator {
69            DeclareBlob::DISCRIMINATOR => {
70                let data = compiled_instruction.data.get(8..).unwrap_or_default();
71                DeclareBlob::try_from_slice(data)
72                    .map(RelevantInstruction::DeclareBlob)
73                    .ok()
74            }
75            InsertChunk::DISCRIMINATOR => {
76                let data = compiled_instruction.data.get(8..).unwrap_or_default();
77                InsertChunk::try_from_slice(data)
78                    .map(RelevantInstruction::InsertChunk)
79                    .ok()
80            }
81            FinalizeBlob::DISCRIMINATOR => {
82                let data = compiled_instruction.data.get(8..).unwrap_or_default();
83                FinalizeBlob::try_from_slice(data)
84                    .map(RelevantInstruction::FinalizeBlob)
85                    .ok()
86            }
87            // If we don't recognize the discriminator, we ignore the instruction - there might be
88            // more instructions packed into the same transaction which might not be relevant to
89            // us.
90            _ => None,
91        }
92    }
93}
94
95/// A deserialized relevant instruction, containing the blob and blober pubkeys and the instruction.
96pub struct RelevantInstructionWithAccounts {
97    pub blob: Pubkey,
98    pub blober: Pubkey,
99    pub instruction: RelevantInstruction,
100}
101
102/// Deserialize relevant instructions from a transaction, given the indices of the blob and blober
103/// accounts in the transaction.
104pub fn deserialize_relevant_instructions(
105    tx: &VersionedTransaction,
106    blob_pubkey_index: usize,
107    blober_pubkey_index: usize,
108) -> Vec<RelevantInstructionWithAccounts> {
109    tx.message
110        .instructions()
111        .iter()
112        .filter_map(|compiled_instruction| {
113            Some(RelevantInstructionWithAccounts {
114                blob: get_account_at_index(tx, compiled_instruction, blob_pubkey_index)?,
115                blober: get_account_at_index(tx, compiled_instruction, blober_pubkey_index)?,
116                instruction: RelevantInstruction::try_from_slice(compiled_instruction)?,
117            })
118        })
119        .collect()
120}
121
122/// Extract relevant instructions from a list of transactions.
123pub fn extract_relevant_instructions(
124    transactions: &[VersionedTransaction],
125) -> Vec<RelevantInstructionWithAccounts> {
126    transactions
127        .iter()
128        .flat_map(|tx| deserialize_relevant_instructions(tx, 0, 1))
129        .collect()
130}
131
132/// Performs the double-lookup required to find an account at a given account index in an instruction.
133/// This is required because the accounts are not stored in the instruction directly, but in a separate
134/// account list. It is computed as `payload.account_keys[instruction.accounts[index]]`.
135pub fn get_account_at_index(
136    tx: &VersionedTransaction,
137    instruction: &CompiledInstruction,
138    index: usize,
139) -> Option<Pubkey> {
140    let actual_index = *instruction.accounts.get(index)? as usize;
141    tx.message.static_account_keys().get(actual_index).copied()
142}