nitro_da_indexer_api/
lib.rs1use 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#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
20pub enum CompoundProof {
21 Inclusion(CompoundInclusionProof),
23 Completeness(CompoundCompletenessProof),
25}
26
27#[rpc(server, client)]
29pub trait IndexerRpc {
30 #[method(name = "get_blobs")]
34 async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
35
36 #[method(name = "get_proof")]
39 async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
40
41 #[method(name = "add_blobers")]
43 async fn add_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
44
45 #[method(name = "remove_blobers")]
47 async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
48
49 #[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
56pub 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 _ => None,
91 }
92 }
93}
94
95pub struct RelevantInstructionWithAccounts {
97 pub blob: Pubkey,
98 pub blober: Pubkey,
99 pub instruction: RelevantInstruction,
100}
101
102pub 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
122pub 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
132pub 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}