1use std::{collections::HashMap, fmt::Display};
2
3use crossbeam_channel::Sender;
4use jsonrpc_core::Result as RpcError;
5use locker::SurfnetSvmLocker;
6use solana_account::Account;
7use solana_account_decoder::{UiAccount, UiAccountEncoding};
8use solana_client::{
9 rpc_config::RpcTransactionLogsFilter,
10 rpc_filter::RpcFilterType,
11 rpc_response::{RpcKeyedAccount, RpcLogsResponse},
12};
13use solana_clock::Slot;
14use solana_commitment_config::CommitmentLevel;
15use solana_epoch_info::EpochInfo;
16use solana_pubkey::Pubkey;
17use solana_signature::Signature;
18use solana_transaction::versioned::VersionedTransaction;
19use solana_transaction_error::TransactionError;
20use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, TransactionStatus};
21use svm::SurfnetSvm;
22
23use crate::{
24 error::{SurfpoolError, SurfpoolResult},
25 types::{GeyserAccountUpdate, TransactionWithStatusMeta},
26};
27
28pub mod locker;
29pub mod remote;
30pub mod surfnet_lite_svm;
31pub mod svm;
32
33pub const FINALIZATION_SLOT_THRESHOLD: u64 = 31;
34pub const SLOTS_PER_EPOCH: u64 = 432000;
35
36pub type AccountFactory = Box<dyn Fn(SurfnetSvmLocker) -> GetAccountResult + Send + Sync>;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum GeyserSlotStatus {
42 Processed,
44 Rooted,
46 Confirmed,
48}
49
50#[derive(Debug, Clone)]
52pub struct GeyserBlockMetadata {
53 pub slot: Slot,
54 pub blockhash: String,
55 pub parent_slot: Slot,
56 pub parent_blockhash: String,
57 pub block_time: Option<i64>,
58 pub block_height: Option<u64>,
59 pub executed_transaction_count: u64,
60 pub entry_count: u64,
61}
62
63#[derive(Debug, Clone)]
66pub struct GeyserEntryInfo {
67 pub slot: Slot,
68 pub index: usize,
69 pub num_hashes: u64,
70 pub hash: Vec<u8>,
71 pub executed_transaction_count: u64,
72 pub starting_transaction_index: usize,
73}
74
75#[allow(clippy::large_enum_variant)]
76pub enum GeyserEvent {
77 NotifyTransaction(TransactionWithStatusMeta, Option<VersionedTransaction>),
78 UpdateAccount(GeyserAccountUpdate),
79 StartupAccountUpdate(GeyserAccountUpdate),
82 EndOfStartup,
84 UpdateSlotStatus {
86 slot: Slot,
87 parent: Option<Slot>,
88 status: GeyserSlotStatus,
89 },
90 NotifyBlockMetadata(GeyserBlockMetadata),
92 NotifyEntry(GeyserEntryInfo),
94}
95
96#[derive(Debug, Eq, PartialEq, Hash, Clone)]
97pub struct BlockIdentifier {
98 pub index: u64,
99 pub hash: String,
100}
101
102impl BlockIdentifier {
103 pub fn zero() -> Self {
104 Self::new(
105 0,
106 "0000000000000000000000000000000000000000000000000000000000000000",
107 )
108 }
109
110 pub fn new(index: u64, hash: &str) -> Self {
111 Self {
112 index,
113 hash: hash.to_string(),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct BlockHeader {
120 pub hash: String,
121 pub previous_blockhash: String,
122 pub parent_slot: Slot,
123 pub block_time: i64,
124 pub block_height: u64,
125 pub signatures: Vec<Signature>,
126}
127
128#[derive(PartialEq, Eq, Clone)]
129pub enum SurfnetDataConnection {
130 Offline,
131 Connected(String, EpochInfo),
132}
133
134pub type SignatureSubscriptionData = (
135 SignatureSubscriptionType,
136 Sender<(Slot, Option<TransactionError>)>,
137);
138
139pub type AccountSubscriptionData =
140 HashMap<Pubkey, Vec<(Option<UiAccountEncoding>, Sender<UiAccount>)>>;
141
142pub type ProgramSubscriptionData = HashMap<
143 Pubkey,
144 Vec<(
145 Option<UiAccountEncoding>,
146 Option<Vec<RpcFilterType>>,
147 Sender<RpcKeyedAccount>,
148 )>,
149>;
150
151pub type LogsSubscriptionData = (
152 CommitmentLevel,
153 RpcTransactionLogsFilter,
154 Sender<(Slot, RpcLogsResponse)>,
155);
156
157pub type SnapshotSubscriptionData = Sender<SnapshotImportNotification>;
158
159#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
160pub struct SnapshotImportNotification {
161 pub snapshot_id: String,
162 pub status: SnapshotImportStatus,
163 pub accounts_loaded: u64,
164 pub total_accounts: u64,
165 pub error: Option<String>,
166}
167
168#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
169pub enum SnapshotImportStatus {
170 Started,
171 InProgress,
172 Completed,
173 Failed,
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub enum SignatureSubscriptionType {
178 Received,
179 Commitment(CommitmentLevel),
180}
181
182impl Display for SignatureSubscriptionType {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 match self {
185 SignatureSubscriptionType::Received => write!(f, "received"),
186 SignatureSubscriptionType::Commitment(level) => write!(f, "{level}"),
187 }
188 }
189}
190
191type DoUpdateSvm = bool;
192
193#[derive(Clone, Debug)]
194pub enum GetAccountResult {
196 None(Pubkey),
198 FoundAccount(Pubkey, Account, DoUpdateSvm),
204 FoundProgramAccount((Pubkey, Account), (Pubkey, Option<Account>)),
205 FoundTokenAccount((Pubkey, Account), (Pubkey, Option<Account>)),
206}
207
208impl GetAccountResult {
209 pub fn expected_data(&self) -> &Vec<u8> {
210 match &self {
211 Self::None(_) => unreachable!(),
212 Self::FoundAccount(_, account, _)
213 | Self::FoundProgramAccount((_, account), _)
214 | Self::FoundTokenAccount((_, account), _) => &account.data,
215 }
216 }
217
218 pub fn apply_update<T>(&mut self, update: T) -> RpcError<()>
219 where
220 T: Fn(&mut Account) -> RpcError<()>,
221 {
222 match self {
223 Self::None(_) => unreachable!(),
224 Self::FoundAccount(_, account, do_update_account) => {
225 update(account)?;
226 *do_update_account = true;
227 }
228 Self::FoundProgramAccount((_, account), _) => {
229 update(account)?;
230 }
231 Self::FoundTokenAccount((_, account), _) => {
232 update(account)?;
233 }
234 }
235 Ok(())
236 }
237
238 pub fn map_account(self) -> SurfpoolResult<Account> {
239 match self {
240 Self::None(pubkey) => Err(SurfpoolError::account_not_found(pubkey)),
241 Self::FoundAccount(_, account, _)
242 | Self::FoundProgramAccount((_, account), _)
243 | Self::FoundTokenAccount((_, account), _) => Ok(account),
244 }
245 }
246
247 #[allow(clippy::type_complexity)]
248 pub fn map_account_with_token_data(
249 self,
250 ) -> Option<((Pubkey, Account), Option<(Pubkey, Option<Account>)>)> {
251 match self {
252 Self::None(_) => None,
253 Self::FoundAccount(pubkey, account, _) => Some(((pubkey, account), None)),
254 Self::FoundProgramAccount((pubkey, account), _) => Some(((pubkey, account), None)),
255 Self::FoundTokenAccount((pubkey, account), token_data) => {
256 Some(((pubkey, account), Some(token_data)))
257 }
258 }
259 }
260
261 pub const fn is_none(&self) -> bool {
262 matches!(self, Self::None(_))
263 }
264
265 pub const fn requires_update(&self) -> bool {
266 match self {
267 Self::None(_) => false,
268 Self::FoundAccount(_, _, do_update) => *do_update,
269 Self::FoundProgramAccount(_, _) => true,
270 Self::FoundTokenAccount(_, _) => true,
271 }
272 }
273}
274
275impl From<GetAccountResult> for Result<Account, SurfpoolError> {
276 fn from(value: GetAccountResult) -> Self {
277 value.map_account()
278 }
279}
280
281impl SignatureSubscriptionType {
282 pub const fn received() -> Self {
283 SignatureSubscriptionType::Received
284 }
285
286 pub const fn processed() -> Self {
287 SignatureSubscriptionType::Commitment(CommitmentLevel::Processed)
288 }
289
290 pub const fn confirmed() -> Self {
291 SignatureSubscriptionType::Commitment(CommitmentLevel::Confirmed)
292 }
293
294 pub const fn finalized() -> Self {
295 SignatureSubscriptionType::Commitment(CommitmentLevel::Finalized)
296 }
297}
298
299#[allow(clippy::large_enum_variant)]
300pub enum GetTransactionResult {
301 None(Signature),
302 FoundTransaction(
303 Signature,
304 EncodedConfirmedTransactionWithStatusMeta,
305 TransactionStatus,
306 ),
307}
308
309impl GetTransactionResult {
310 pub fn found_transaction(
311 signature: Signature,
312 tx: EncodedConfirmedTransactionWithStatusMeta,
313 latest_absolute_slot: u64,
314 ) -> Self {
315 let is_finalized = latest_absolute_slot >= tx.slot + FINALIZATION_SLOT_THRESHOLD;
316 let is_confirmed = latest_absolute_slot >= tx.slot + 1;
317 let (confirmation_status, confirmations) = if is_finalized {
318 (
319 Some(solana_transaction_status::TransactionConfirmationStatus::Finalized),
320 None,
321 )
322 } else if is_confirmed {
323 (
324 Some(solana_transaction_status::TransactionConfirmationStatus::Confirmed),
325 Some((latest_absolute_slot - tx.slot) as usize),
326 )
327 } else {
328 (
329 Some(solana_transaction_status::TransactionConfirmationStatus::Processed),
330 Some((latest_absolute_slot - tx.slot) as usize),
331 )
332 };
333 let status = TransactionStatus {
334 slot: tx.slot,
335 confirmations,
336 status: tx
337 .transaction
338 .clone()
339 .meta
340 .map_or(Ok(()), |m| m.status.map_err(|e| e.into())),
341 err: tx
342 .transaction
343 .clone()
344 .meta
345 .and_then(|m| m.err.map(|e| e.into())),
346 confirmation_status,
347 };
348
349 Self::FoundTransaction(signature, tx, status)
350 }
351
352 pub const fn is_none(&self) -> bool {
353 matches!(self, Self::None(_))
354 }
355
356 pub fn map_found_transaction(&self) -> SurfpoolResult<TransactionStatus> {
357 match self {
358 Self::None(sig) => Err(SurfpoolError::transaction_not_found(sig)),
359 Self::FoundTransaction(_, _, status) => Ok(status.clone()),
360 }
361 }
362
363 pub fn map_some_transaction_status(&self) -> Option<TransactionStatus> {
364 match self {
365 Self::None(_) => None,
366 Self::FoundTransaction(_, _, status) => Some(status.clone()),
367 }
368 }
369}