nitro_da_indexer_api/
lib.rs

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