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#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
23pub enum CompoundProof {
24 Inclusion(CompoundInclusionProof),
26 Completeness(CompoundCompletenessProof),
28}
29
30#[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#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub struct TimeRange {
42 pub start: Option<DateTime<Utc>>,
44 pub end: Option<DateTime<Utc>>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub struct BlobsByBlober {
51 pub blober: Pubkey,
53 #[serde(flatten)]
55 pub time_range: TimeRange,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub struct BlobsByPayer {
61 pub payer: Pubkey,
63 pub network_name: String,
65 #[serde(flatten)]
67 pub time_range: TimeRange,
68}
69
70#[rpc(server, client)]
72pub trait IndexerRpc {
73 #[method(name = "get_blobs")]
77 async fn get_blobs(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<Vec<Vec<u8>>>>;
78
79 #[method(name = "get_blobs_by_blober")]
82 async fn get_blobs_by_blober(&self, blober: BlobsByBlober) -> RpcResult<Vec<Vec<u8>>>;
83
84 #[method(name = "get_blobs_by_payer")]
87 async fn get_blobs_by_payer(&self, payer: BlobsByPayer) -> RpcResult<Vec<Vec<u8>>>;
88
89 #[method(name = "get_proof")]
92 async fn get_proof(&self, blober: Pubkey, slot: u64) -> RpcResult<Option<CompoundProof>>;
93
94 #[method(name = "get_proof_for_blob")]
97 async fn get_proof_for_blob(&self, blob_address: Pubkey) -> RpcResult<Option<CompoundProof>>;
98
99 #[method(name = "add_blobers")]
101 async fn add_blobers(&self, blobers: HashSet<BloberData>) -> RpcResult<()>;
102
103 #[method(name = "remove_blobers")]
105 async fn remove_blobers(&self, blobers: HashSet<Pubkey>) -> RpcResult<()>;
106
107 #[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
114pub 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 _ => None,
149 }
150 }
151}
152
153pub struct RelevantInstructionWithAccounts {
155 pub blob: Pubkey,
156 pub blober: Pubkey,
157 pub instruction: RelevantInstruction,
158}
159
160pub 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
180pub 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
190pub 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}