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