sfm_sdk/
lib.rs

1use std::collections::HashMap;
2use std::fmt::Display;
3use std::iter::Map;
4use ts_rs::TS;
5
6#[macro_use]
7extern crate string_enum;
8
9pub mod helpers;
10pub mod processors;
11pub mod utils;
12
13use serde::{Deserialize, Serialize};
14use solana_program::clock::UnixTimestamp;
15use solana_sdk::transaction::TransactionError;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::Arc;
18use tokio::select;
19use tokio::signal::unix::{signal, SignalKind};
20use tokio::sync::watch::{Receiver, Sender};
21use tracing::info;
22
23#[derive(Clone, Serialize, Deserialize)]
24pub struct QueueItem {
25    pub numbers: Vec<u64>,
26    pub programs: Vec<String>,
27}
28
29#[derive(Clone, Serialize, Deserialize)]
30pub struct TransactionProgram {
31    /// Transaction hash..
32    pub transaction_hash: String,
33    /// The programs involved in the transaction
34    pub program: String,
35    /// Time of transaction
36    pub timestamp: u64,
37}
38
39pub type AccountMap = Map<String, AccountLatest>;
40
41/// Struct that stashes the latest bits of an account.
42pub struct AccountLatest {
43    /// The balance in the account.  Modifiable by programs.
44    pub balance: u64,
45    /// The data held in this account.  Modifiable by programs.
46    pub data: Vec<u8>,
47    /// Account that owns this account, defaults to System program.
48    pub owner: String,
49    /// The epoch at which this account will next owe rent
50    pub rent_epoch: u64,
51    /// The optional mint relevant to this account (At that block in time)
52    pub mint: Option<String>,
53    /// Timestamp at point in time relevant to block
54    pub timestamp: u64,
55    /// Block at point in time
56    pub block: u64,
57}
58
59/// Account Hash, Owner (New), Mint (New), Amount (New), Timestamp, Block
60/// pub type AccountEntry = (String, Option<String>, Option<String>, Option<u64>, u64, u64);
61pub struct AccountEntry {
62    /// The unique identifier for the account
63    pub hash: String,
64    /// The optional amount relevant to this account (At that block in time)
65    pub balance: Option<u64>,
66    /// The optional data relevant to this account (At that block in time, and if there is any data
67    /// for diff generation)
68    pub data: Option<Vec<u8>>,
69    /// The map that differentiates vLatest and the vUpcoming
70    pub data_diff_map: Option<HashMap<usize, u8>>,
71    /// The optional owner relevant to this account (At that block in time)
72    pub owner: Option<String>,
73    /// The optional mint relevant to this account (At that block in time)
74    pub mint: Option<String>,
75    /// Timestamp at point in time relevant to block
76    pub timestamp: u64,
77    /// Block at point in time
78    pub block: u64,
79}
80
81#[derive(Clone, Serialize, Deserialize)]
82pub struct AccountProgram {
83    // The account pub key relating to this transaction.
84    pub account: String,
85    // The programs involved with the account
86    pub program: String,
87    // Block number responsible for this instance.
88    pub block: u64,
89    // Time when it is created according to Solana's on-chain clock
90    pub timestamp: u64,
91}
92
93#[derive(Clone, Serialize, Deserialize)]
94pub struct AccountTransaction {
95    // The transaction this transaction belongs to.
96    pub transaction_hash: String,
97    // The account pub key relating to this transaction.
98    pub account: String,
99    // Block number responsible for this instance.
100    pub block: u64,
101    // Time when it is created according to Solana's on-chain clock
102    pub timestamp: u64,
103}
104
105impl PartialEq for AccountTransaction {
106    fn eq(&self, other: &Self) -> bool {
107        self.block == other.block
108            && self.account == other.account
109            && self.timestamp == other.timestamp
110    }
111}
112
113#[derive(Clone, Serialize, Deserialize)]
114pub struct BatchedAccountTransaction {
115    // Transaction hashes relevant to this account at this point in time
116    pub hashes: Vec<String>,
117    // Time when it is created according to Solana's on-chain clock
118    pub timestamp: u64,
119}
120
121#[derive(Clone, Serialize, Deserialize, TS)]
122#[ts(export)]
123pub struct Block {
124    /// Basically the epoch this block belongs to
125    pub epoch: u32,
126    /// Parent block hash of the current block
127    pub previous_hash: String,
128    /// Validator producing said block
129    pub producer: String,
130    /// This block's hash
131    pub hash: String,
132    /// Parent's block number
133    pub parent_number: u64,
134    /// This block's number
135    pub number: u64,
136    /// Amount of data contained within the block
137    pub data_size: u64,
138    /// Total count of transactions in the block
139    pub number_of_transactions: u32,
140    /// Total number of successful transactions
141    pub successful_transactions: u32,
142    /// Total number of vote-related transactions
143    pub vote_transactions: u32,
144    /// Total transaction fees accumulated in the transactions within this block
145    pub total_tx_fees: u64,
146    /// Total number of rewards
147    pub number_of_rewards: u32,
148    /// Total amount of rewards accrued in this block
149    pub total_reward_amount: u64,
150    /// Total amount of compute units consumed
151    pub total_compute_units_consumed: u64,
152    /// Absolute limit of compute units
153    pub total_compute_units_limit: u64,
154    /// Time this block was proposed
155    pub block_time: u64,
156}
157
158#[derive(Clone, Serialize, Deserialize)]
159pub enum VersionedTransfer {
160    V0(Transfer),
161    V1(EnrichedTransfer),
162}
163
164/// Routing key
165/// <account>#<mint>#<timestamp>
166/// Schema column qualifiers
167/// type —> receive | send
168/// amount (in finalised format, e.g. 0.000031 <eth, excluded>)
169/// txHash
170#[derive(Clone, Serialize, Deserialize)]
171pub struct EnrichedTransfer {
172    // Dictates the actions resulting in this transfer.
173    pub action: String,
174    // Index of instruction (<Parent index>|<Index>),
175    pub index: String,
176    // Status of the transaction,
177    pub status: EnrichedTransferStatus,
178    // The account that will give up the amount.
179    pub source: String,
180    // Should this be a token-based transfer, this will be the associated token account of the source.
181    pub source_association: Option<String>,
182    // The account that will receive the amount.
183    pub destination: String,
184    // Should this be a token-based transfer, this will be the associated token account of the destination.
185    pub destination_association: Option<String>,
186    // If this is empty, the balance relates to lamports. If its NOT empty, the balance relates to the
187    // token in question.
188    pub token: Option<String>,
189    // Decimals for the token
190    pub decimals: Option<u8>,
191    // The amount transferred
192    pub amount: u64,
193    // Epoch time for when this input was added to the db.
194    pub timestamp: u64,
195}
196
197/// Record an instance of a transaction transfer at any given time.
198/// Routing key
199/// <account>#<mint>#<timestamp>
200/// Schema column qualifiers
201/// type —> receive | send
202/// amount (in finalised format, e.g. 0.000031 <eth, excluded>)
203/// txHash
204///
205#[derive(Clone, Serialize, Deserialize)]
206pub struct Transfer {
207    // The transaction this instruction belongs to.
208    pub transaction_hash: String,
209    // Status of the transaction,
210    pub status: u16,
211    // The account that will give up the amount.
212    pub source: String,
213    // Should this be a token-based transfer, this will be the associated token account of the source.
214    pub source_association: Option<String>,
215    // The account that will receive the amount.
216    pub destination: String,
217    // Should this be a token-based transfer, this will be the associated token account of the destination.
218    pub destination_association: Option<String>,
219    // If this is empty, the balance relates to lamports. If its NOT empty, the balance relates to the
220    // token in question.
221    pub token: Option<String>,
222    // The amount transferred
223    pub amount: u64,
224    // Epoch time for when this input was added to the db.
225    pub timestamp: u64,
226}
227
228impl From<EnrichedTransfer> for Transfer {
229    fn from(transfer: EnrichedTransfer) -> Self {
230        Self {
231            transaction_hash: "".into(),
232            status: transfer.status as u16,
233            source: transfer.source,
234            source_association: transfer.source_association,
235            destination: transfer.destination,
236            destination_association: transfer.destination_association,
237            token: transfer.token,
238            amount: transfer.amount,
239            timestamp: transfer.timestamp,
240        }
241    }
242}
243
244#[derive(Clone, Serialize, Deserialize)]
245pub struct BTEnrichedTransfer {
246    // Dictates the actions resulting in this transfer.
247    pub action: String,
248    // Index of instruction (<Parent index>|<Index>),
249    pub index: String,
250    // Status of the transaction,
251    pub status: String,
252    // The account that will give up the amount.
253    pub source: String,
254    // Should this be a token-based transfer, this will be the associated token account of the source.
255    #[serde(rename(serialize = "sourceAssociation"))]
256    pub source_association: String,
257    // The account that will receive the amount.
258    pub destination: String,
259    // Should this be a token-based transfer, this will be the associated token account of the destination.
260    #[serde(rename(serialize = "destinationAssociation"))]
261    pub destination_association: String,
262    // If this is empty, the balance relates to lamports. If its NOT empty, the balance relates to the
263    // token in question.
264    pub token: String,
265    // Decimals of the token
266    pub decimals: Option<u8>,
267    // The amount transferred
268    pub amount: u64,
269    // Epoch time for when this input was added to the db.
270    pub timestamp: u64,
271}
272
273impl From<EnrichedTransfer> for BTEnrichedTransfer {
274    fn from(trf: EnrichedTransfer) -> Self {
275        BTEnrichedTransfer {
276            action: trf.action,
277            index: trf.index,
278            status: trf.status.to_string(),
279            source: trf.source,
280            source_association: if let Some(source_association) = trf.source_association {
281                source_association
282            } else {
283                "".to_string()
284            },
285            destination: trf.destination,
286            destination_association: if let Some(destination_association) =
287                trf.destination_association
288            {
289                destination_association
290            } else {
291                "".to_string()
292            },
293            token: if let Some(token) = trf.token {
294                token
295            } else {
296                "".to_string()
297            },
298            decimals: trf.decimals,
299            amount: trf.amount,
300            timestamp: trf.timestamp,
301        }
302    }
303}
304
305#[derive(Clone, Serialize, Deserialize)]
306#[serde(rename_all = "camelCase")]
307pub struct JsonEnrichedTransfer {
308    // Dictates the actions resulting in this transfer.
309    pub action: String,
310    // Index of instruction (<Parent index>|<Index>),
311    pub index: String,
312    // Status of the transaction,
313    pub status: String,
314    // The account that will give up the amount.
315    pub source: String,
316    // Should this be a token-based transfer, this will be the associated token account of the source.
317    pub source_association: String,
318    // The account that will receive the amount.
319    pub destination: String,
320    // Should this be a token-based transfer, this will be the associated token account of the destination.
321    pub destination_association: String,
322    // If this is empty, the balance relates to lamports. If its NOT empty, the balance relates to the
323    // token in question.
324    pub token: String,
325    // The amount transferred
326    pub amount: u64,
327    // Epoch time for when this input was added to the db.
328    pub timestamp: u64,
329}
330
331impl From<Option<TransactionError>> for EnrichedTransferStatus {
332    fn from(err: Option<TransactionError>) -> Self {
333        match err {
334            Some(error) => match error {
335                TransactionError::AccountInUse => Self::AccountInUse,
336                TransactionError::AccountLoadedTwice => Self::AccountLoadedTwice,
337                TransactionError::AccountNotFound => Self::AccountNotFound,
338                TransactionError::ProgramAccountNotFound => Self::ProgramAccountNotFound,
339                TransactionError::InsufficientFundsForFee => Self::InsufficientFundsForFee,
340                TransactionError::InvalidAccountForFee => Self::InvalidAccountForFee,
341                TransactionError::AlreadyProcessed => Self::AlreadyProcessed,
342                TransactionError::BlockhashNotFound => Self::BlockhashNotFound,
343                TransactionError::InstructionError(_, _) => Self::InstructionError,
344                TransactionError::CallChainTooDeep => Self::CallChainTooDeep,
345                TransactionError::MissingSignatureForFee => Self::MissingSignatureForFee,
346                TransactionError::InvalidAccountIndex => Self::InvalidAccountIndex,
347                TransactionError::SignatureFailure => Self::SignatureFailure,
348                TransactionError::InvalidProgramForExecution => Self::InvalidProgramForExecution,
349                TransactionError::SanitizeFailure => Self::SanitizeFailure,
350                TransactionError::ClusterMaintenance => Self::ClusterMaintenance,
351                TransactionError::AccountBorrowOutstanding => Self::AccountBorrowOutstanding,
352                TransactionError::WouldExceedMaxBlockCostLimit => {
353                    Self::WouldExceedMaxBlockCostLimit
354                }
355                TransactionError::UnsupportedVersion => Self::UnsupportedVersion,
356                TransactionError::InvalidWritableAccount => Self::InvalidWritableAccount,
357                TransactionError::WouldExceedMaxAccountCostLimit => {
358                    Self::WouldExceedMaxAccountCostLimit
359                }
360                TransactionError::WouldExceedAccountDataBlockLimit => {
361                    Self::WouldExceedAccountDataBlockLimit
362                }
363                TransactionError::TooManyAccountLocks => Self::TooManyAccountLocks,
364                TransactionError::AddressLookupTableNotFound => Self::AddressLookupTableNotFound,
365                TransactionError::InvalidAddressLookupTableOwner => {
366                    Self::InvalidAddressLookupTableOwner
367                }
368                TransactionError::InvalidAddressLookupTableData => {
369                    Self::InvalidAddressLookupTableData
370                }
371                TransactionError::InvalidAddressLookupTableIndex => {
372                    Self::InvalidAddressLookupTableIndex
373                }
374                TransactionError::InvalidRentPayingAccount => Self::InvalidRentPayingAccount,
375                TransactionError::WouldExceedMaxVoteCostLimit => Self::WouldExceedMaxVoteCostLimit,
376                TransactionError::WouldExceedAccountDataTotalLimit => {
377                    Self::WouldExceedAccountDataTotalLimit
378                }
379                TransactionError::DuplicateInstruction(_) => Self::DuplicateInstruction,
380                TransactionError::InsufficientFundsForRent { .. } => Self::InsufficientFundsForRent,
381                TransactionError::MaxLoadedAccountsDataSizeExceeded => {
382                    Self::MaxLoadedAccountsDataSizeExceeded
383                }
384                TransactionError::InvalidLoadedAccountsDataSizeLimit => {
385                    Self::InvalidLoadedAccountsDataSizeLimit
386                }
387                TransactionError::ResanitizationNeeded => Self::ResanitizationNeeded,
388                TransactionError::UnbalancedTransaction => Self::UnbalancedTransaction,
389                TransactionError::ProgramExecutionTemporarilyRestricted { account_index: _ } => {
390                    Self::ProgramExecutionTemporarilyRestricted
391                }
392            },
393            None => Self::Successful,
394        }
395    }
396}
397
398#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
399pub enum EnrichedTransferStatus {
400    /// `Unknown`
401    Null = 0,
402    /// `Successful`
403    Successful = 1,
404    /// `AccountInUse`
405    AccountInUse = 2,
406    /// `AccountLoadedTwice`
407    AccountLoadedTwice = 3,
408    /// `AccountNotFound`
409    AccountNotFound = 4,
410    /// `ProgramAccountNotFound`
411    ProgramAccountNotFound = 5,
412    /// `InsufficientFundsForFee`
413    InsufficientFundsForFee = 6,
414    /// `InvalidAccountForFee`
415    InvalidAccountForFee = 7,
416    /// `AlreadyProcessed`
417    AlreadyProcessed = 8,
418    /// `BlockhashNotFound`
419    BlockhashNotFound = 9,
420    /// `InstructionError`
421    InstructionError = 10,
422    /// `CallChainTooDeep`
423    CallChainTooDeep = 11,
424    /// `MissingSignatureForFee`
425    MissingSignatureForFee = 12,
426    /// `InvalidAccountIndex`
427    InvalidAccountIndex = 13,
428    /// `SignatureFailure`
429    SignatureFailure = 14,
430    /// `InvalidProgramForExecution`
431    InvalidProgramForExecution = 15,
432    /// `SanitizeFailure`
433    SanitizeFailure = 16,
434    /// `ClusterMaintenance`
435    ClusterMaintenance = 17,
436    /// `AccountBorrowOutstanding`
437    AccountBorrowOutstanding = 18,
438    /// `WouldExceedMaxBlockCostLimit`
439    WouldExceedMaxBlockCostLimit = 19,
440    /// `UnsupportedVersion`
441    UnsupportedVersion = 20,
442    /// `InvalidWritableAccount`
443    InvalidWritableAccount = 21,
444    /// `WouldExceedMaxAccountCostLimit`
445    WouldExceedMaxAccountCostLimit = 22,
446    /// `WouldExceedAccountDataBlockLimit`
447    WouldExceedAccountDataBlockLimit = 23,
448    /// `TooManyAccountLocks`
449    TooManyAccountLocks = 24,
450    /// `AddressLookupTableNotFound`
451    AddressLookupTableNotFound = 25,
452    /// `InvalidAddressLookupTableOwner`
453    InvalidAddressLookupTableOwner = 26,
454    /// `InvalidAddressLookupTableData`
455    InvalidAddressLookupTableData = 27,
456    /// `InvalidAddressLookupTableIndex`
457    InvalidAddressLookupTableIndex = 28,
458    /// `InvalidRentPayingAccount`
459    InvalidRentPayingAccount = 29,
460    /// `WouldExceedMaxVoteCostLimit`
461    WouldExceedMaxVoteCostLimit = 30,
462    /// `WouldExceedAccountDataTotalLimit`
463    WouldExceedAccountDataTotalLimit = 31,
464    /// `DuplicateInstruction`
465    DuplicateInstruction = 32,
466    /// `InsufficientFundsForRent`
467    InsufficientFundsForRent = 33,
468    /// `MaxLoadedAccountsDataSizeExceeded`
469    MaxLoadedAccountsDataSizeExceeded = 34,
470    /// `InvalidLoadedAccountsDataSizeLimit`
471    InvalidLoadedAccountsDataSizeLimit = 35,
472    /// `ResanitizationNeeded`
473    ResanitizationNeeded = 36,
474    /// `UnbalancedTransaction`
475    UnbalancedTransaction = 37,
476    /// ProgramExecutionTemporarilyRestricted
477    ProgramExecutionTemporarilyRestricted = 38,
478}
479
480impl Display for EnrichedTransferStatus {
481    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
482        match self {
483            EnrichedTransferStatus::Null => write!(f, "Null"),
484            EnrichedTransferStatus::Successful => write!(f, "Successful"),
485            EnrichedTransferStatus::AccountInUse => write!(f, "AccountInUse"),
486            EnrichedTransferStatus::AccountLoadedTwice => write!(f, "AccountLoadedTwice"),
487            EnrichedTransferStatus::AccountNotFound => write!(f, "AccountNotFound"),
488            EnrichedTransferStatus::ProgramAccountNotFound => write!(f, "ProgramAccountNotFound"),
489            EnrichedTransferStatus::InsufficientFundsForFee => write!(f, "InsufficientFundsForFee"),
490            EnrichedTransferStatus::InvalidAccountForFee => write!(f, "InvalidAccountForFee"),
491            EnrichedTransferStatus::AlreadyProcessed => write!(f, "AlreadyProcessed"),
492            EnrichedTransferStatus::BlockhashNotFound => write!(f, "BlockhashNotFound"),
493            EnrichedTransferStatus::InstructionError => write!(f, "InstructionError"),
494            EnrichedTransferStatus::CallChainTooDeep => write!(f, "CallChainTooDeep"),
495            EnrichedTransferStatus::MissingSignatureForFee => write!(f, "MissingSignatureForFee"),
496            EnrichedTransferStatus::InvalidAccountIndex => write!(f, "InvalidAccountIndex"),
497            EnrichedTransferStatus::SignatureFailure => write!(f, "SignatureFailure"),
498            EnrichedTransferStatus::InvalidProgramForExecution => {
499                write!(f, "InvalidProgramForExecution")
500            }
501            EnrichedTransferStatus::SanitizeFailure => write!(f, "SanitizeFailure"),
502            EnrichedTransferStatus::ClusterMaintenance => write!(f, "ClusterMaintenance"),
503            EnrichedTransferStatus::AccountBorrowOutstanding => {
504                write!(f, "AccountBorrowOutstanding")
505            }
506            EnrichedTransferStatus::WouldExceedMaxBlockCostLimit => {
507                write!(f, "WouldExceedMaxBlockCostLimit")
508            }
509            EnrichedTransferStatus::UnsupportedVersion => write!(f, "UnsupportedVersion"),
510            EnrichedTransferStatus::InvalidWritableAccount => write!(f, "InvalidWritableAccount"),
511            EnrichedTransferStatus::WouldExceedMaxAccountCostLimit => {
512                write!(f, "WouldExceedMaxAccountCostLimit")
513            }
514            EnrichedTransferStatus::WouldExceedAccountDataBlockLimit => {
515                write!(f, "WouldExceedAccountDataBlockLimit")
516            }
517            EnrichedTransferStatus::TooManyAccountLocks => write!(f, "TooManyAccountLocks"),
518            EnrichedTransferStatus::AddressLookupTableNotFound => {
519                write!(f, "AddressLookupTableNotFound")
520            }
521            EnrichedTransferStatus::InvalidAddressLookupTableOwner => {
522                write!(f, "InvalidAddressLookupTableOwner")
523            }
524            EnrichedTransferStatus::InvalidAddressLookupTableData => {
525                write!(f, "InvalidAddressLookupTableData")
526            }
527            EnrichedTransferStatus::InvalidAddressLookupTableIndex => {
528                write!(f, "InvalidAddressLookupTableIndex")
529            }
530            EnrichedTransferStatus::InvalidRentPayingAccount => {
531                write!(f, "InvalidRentPayingAccount")
532            }
533            EnrichedTransferStatus::WouldExceedMaxVoteCostLimit => {
534                write!(f, "WouldExceedMaxVoteCostLimit")
535            }
536            EnrichedTransferStatus::WouldExceedAccountDataTotalLimit => {
537                write!(f, "WouldExceedAccountDataTotalLimit")
538            }
539            EnrichedTransferStatus::DuplicateInstruction => write!(f, "DuplicateInstruction"),
540            EnrichedTransferStatus::InsufficientFundsForRent => {
541                write!(f, "InsufficientFundsForRent")
542            }
543            EnrichedTransferStatus::MaxLoadedAccountsDataSizeExceeded => {
544                write!(f, "MaxLoadedAccountsDataSizeExceeded")
545            }
546            EnrichedTransferStatus::InvalidLoadedAccountsDataSizeLimit => {
547                write!(f, "InvalidLoadedAccountsDataSizeLimit")
548            }
549            EnrichedTransferStatus::ResanitizationNeeded => write!(f, "ResanitizationNeeded"),
550            EnrichedTransferStatus::UnbalancedTransaction => write!(f, "UnbalancedTransaction"),
551            EnrichedTransferStatus::ProgramExecutionTemporarilyRestricted => {
552                write!(f, "ProgramExecutionTemporarilyRestricted")
553            }
554        }
555    }
556}
557
558impl EnrichedTransferStatus {
559    pub fn from_u16(value: u16) -> Self {
560        match value {
561            1 => Self::Successful,
562            2 => Self::AccountInUse,
563            3 => Self::AccountLoadedTwice,
564            4 => Self::AccountNotFound,
565            5 => Self::ProgramAccountNotFound,
566            6 => Self::InsufficientFundsForFee,
567            7 => Self::InvalidAccountForFee,
568            8 => Self::AlreadyProcessed,
569            9 => Self::BlockhashNotFound,
570            10 => Self::InstructionError,
571            11 => Self::CallChainTooDeep,
572            12 => Self::MissingSignatureForFee,
573            13 => Self::InvalidAccountIndex,
574            14 => Self::SignatureFailure,
575            15 => Self::InvalidProgramForExecution,
576            16 => Self::SanitizeFailure,
577            17 => Self::ClusterMaintenance,
578            18 => Self::AccountBorrowOutstanding,
579            19 => Self::WouldExceedMaxBlockCostLimit,
580            20 => Self::UnsupportedVersion,
581            21 => Self::InvalidWritableAccount,
582            22 => Self::WouldExceedMaxAccountCostLimit,
583            23 => Self::WouldExceedAccountDataBlockLimit,
584            24 => Self::TooManyAccountLocks,
585            25 => Self::AddressLookupTableNotFound,
586            26 => Self::InvalidAddressLookupTableOwner,
587            27 => Self::InvalidAddressLookupTableData,
588            28 => Self::InvalidAddressLookupTableIndex,
589            29 => Self::InvalidRentPayingAccount,
590            30 => Self::WouldExceedMaxVoteCostLimit,
591            31 => Self::WouldExceedAccountDataTotalLimit,
592            32 => Self::DuplicateInstruction,
593            33 => Self::InsufficientFundsForRent,
594            34 => Self::MaxLoadedAccountsDataSizeExceeded,
595            35 => Self::InvalidLoadedAccountsDataSizeLimit,
596            36 => Self::ResanitizationNeeded,
597            37 => Self::UnbalancedTransaction,
598            38 => Self::ProgramExecutionTemporarilyRestricted,
599            _ => Self::Null,
600        }
601    }
602    pub fn to_16(&self) -> u16 {
603        *self as u16
604    }
605}
606
607#[derive(Clone, Deserialize, Serialize)]
608pub struct TransactionInfo {
609    pub hash: String,
610    pub status: u16,
611    pub fee: u64,
612    pub total_cu_consumed: u64,
613    pub total_cu_limit: u64,
614    pub transfers: Vec<EnrichedTransfer>,
615}
616
617impl TransactionInfo {
618    pub fn old_transfers(&mut self) -> Vec<EnrichedTransfer> {
619        self.transfers.clone()
620    }
621}
622
623#[derive(Clone, Deserialize, Serialize)]
624pub struct ProgramConsumption {
625    pub tx: String,
626    pub status: u16,
627    pub line: u32,
628    pub program: String,
629    pub cu_consumed: u64,
630    pub cu_limit: u64,
631    pub timestamp: UnixTimestamp,
632}
633
634pub fn is_not_transfer(address: &str) -> bool {
635    ![
636        "11111111111111111111111111111111",
637        // Config Program
638        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
639        // Associated Token Account Program
640        "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
641    ]
642    .contains(&address)
643}
644
645/// Listens for the machine shutdown signals via a SysSigListener with a `broadcast::Receiver`.
646///
647/// Shutdown is signalled using a `broadcast::Receiver`. Only a single value is
648/// ever sent. Once a value has been sent via the broadcast channel, the server
649/// should shutdown.
650///
651/// The `Shutdown` struct listens for the signal and tracks that the signal has
652/// been received. Callers may query for whether the shutdown signal has been
653/// received or not.
654#[derive(Debug)]
655pub struct SysSigReceiver {
656    /// `true` if the shutdown signal has been received
657    shutdown: Arc<AtomicBool>,
658
659    /// The receive half of the channel used to listen for shutdown.
660    notify: Receiver<()>,
661}
662
663impl SysSigReceiver {
664    /// Create a new `Shutdown` backed by the given `broadcast::Receiver`.
665    pub fn new(shutdown: Arc<AtomicBool>, notify: Receiver<()>) -> SysSigReceiver {
666        SysSigReceiver { shutdown, notify }
667    }
668
669    /// Returns `true` if the shutdown signal has been received.
670    pub fn is_shutdown(&self) -> bool {
671        self.shutdown.load(Ordering::SeqCst)
672    }
673
674    /// Receive the shutdown notice, waiting if necessary.
675    pub async fn recv(&mut self) {
676        // If the shutdown signal has already been received, then return
677        // immediately.
678        if self.is_shutdown() {
679            return;
680        }
681
682        // Cannot receive a "lag error" as only one value is ever sent.
683        let _ = self.notify.changed().await;
684
685        if !self.is_shutdown() {
686            self.shutdown.store(true, Ordering::SeqCst);
687        }
688    }
689}
690
691/// System Signal Listener
692/// A simple struct to listen for shutdown signals from system, and for listeners to receive a
693/// subscriber pipe.
694pub struct SysSigListener {
695    shutdown: Arc<AtomicBool>,
696    notifier: Sender<()>,
697}
698
699impl SysSigListener {
700    pub fn new(notifier: Sender<()>) -> Self {
701        Self {
702            shutdown: Arc::new(AtomicBool::new(false)),
703            notifier,
704        }
705    }
706
707    /// Initiates the watchdog sequence, listening to signals from the host.
708    pub async fn watchdog(self) {
709        info!("Watchdog turned on!");
710
711        let mut alarm_sig = signal(SignalKind::alarm()).expect("Alarm stream failed.");
712        let mut hangup_sig = signal(SignalKind::hangup()).expect("Hangup stream failed.");
713        let mut int_sig = signal(SignalKind::interrupt()).expect("Interrupt stream failed.");
714        let mut quit_sig = signal(SignalKind::quit()).expect("Quit stream failed.");
715        let mut term_sig = signal(SignalKind::terminate()).expect("Terminate stream failed.");
716
717        select! {
718            _ = tokio::signal::ctrl_c() => {
719                info!("CTRL+C received, terminating now!");
720            },
721            _ = alarm_sig.recv() => {
722                info!("SIGALRM received, terminating now!");
723            }
724            _ = hangup_sig.recv() => {
725                info!("SIGHUP received, terminating now!");
726            }
727            _ = int_sig.recv() => {
728                info!("SIGINT received, terminating now!");
729            }
730            _ = quit_sig.recv() => {
731                info!("SIGQUIT received, terminating now!");
732            }
733            _ = term_sig.recv() => {
734                info!("SIGTERM received, terminating now!");
735            }
736        }
737
738        self.forced_shutdown();
739    }
740
741    pub fn forced_shutdown(self) {
742        info!("Shutting down!");
743        (*self.shutdown).store(true, Ordering::SeqCst);
744
745        drop(self.notifier);
746    }
747
748    /// Retrieves a listener for the watchdog.
749    pub fn get_receiver(&mut self) -> SysSigReceiver {
750        SysSigReceiver::new(Arc::clone(&self.shutdown), self.notifier.subscribe())
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757    use std::time::Duration;
758    use tokio::sync::watch;
759
760    #[tokio::test]
761    async fn test_shutdown() {
762        // Establish a channel broadcaster
763        let (notify_shutdown, _) = watch::channel(());
764        // Establish the SysSigListener with the shutdown notifier (the channel sender)
765        let mut listener = SysSigListener::new(notify_shutdown);
766        // Establish the receiver in conjunction with the listener by obtaining a receiver.
767        let receiver = listener.get_receiver();
768
769        // Simulate listener.watchdog().await; and sleep for 2 seconds before termination.
770        tokio::time::sleep(Duration::from_millis(2000)).await;
771        listener.forced_shutdown();
772
773        assert_eq!(receiver.shutdown.load(Ordering::SeqCst), true);
774    }
775}