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