clone_solana_transaction_error/
lib.rs

1#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#[cfg(feature = "frozen-abi")]
4use clone_solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample};
5#[cfg(feature = "serde")]
6use serde_derive::{Deserialize, Serialize};
7use {
8    clone_solana_instruction::error::InstructionError, clone_solana_sanitize::SanitizeError,
9    core::fmt,
10};
11
12pub type TransactionResult<T> = Result<T, TransactionError>;
13
14/// Reasons a transaction might be rejected.
15#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub enum TransactionError {
19    /// An account is already being processed in another transaction in a way
20    /// that does not support parallelism
21    AccountInUse,
22
23    /// A `Pubkey` appears twice in the transaction's `account_keys`.  Instructions can reference
24    /// `Pubkey`s more than once but the message must contain a list with no duplicate keys
25    AccountLoadedTwice,
26
27    /// Attempt to debit an account but found no record of a prior credit.
28    AccountNotFound,
29
30    /// Attempt to load a program that does not exist
31    ProgramAccountNotFound,
32
33    /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction
34    InsufficientFundsForFee,
35
36    /// This account may not be used to pay transaction fees
37    InvalidAccountForFee,
38
39    /// The bank has seen this transaction before. This can occur under normal operation
40    /// when a UDP packet is duplicated, as a user error from a client not updating
41    /// its `recent_blockhash`, or as a double-spend attack.
42    AlreadyProcessed,
43
44    /// The bank has not seen the given `recent_blockhash` or the transaction is too old and
45    /// the `recent_blockhash` has been discarded.
46    BlockhashNotFound,
47
48    /// An error occurred while processing an instruction. The first element of the tuple
49    /// indicates the instruction index in which the error occurred.
50    InstructionError(u8, InstructionError),
51
52    /// Loader call chain is too deep
53    CallChainTooDeep,
54
55    /// Transaction requires a fee but has no signature present
56    MissingSignatureForFee,
57
58    /// Transaction contains an invalid account reference
59    InvalidAccountIndex,
60
61    /// Transaction did not pass signature verification
62    SignatureFailure,
63
64    /// This program may not be used for executing instructions
65    InvalidProgramForExecution,
66
67    /// Transaction failed to sanitize accounts offsets correctly
68    /// implies that account locks are not taken for this TX, and should
69    /// not be unlocked.
70    SanitizeFailure,
71
72    ClusterMaintenance,
73
74    /// Transaction processing left an account with an outstanding borrowed reference
75    AccountBorrowOutstanding,
76
77    /// Transaction would exceed max Block Cost Limit
78    WouldExceedMaxBlockCostLimit,
79
80    /// Transaction version is unsupported
81    UnsupportedVersion,
82
83    /// Transaction loads a writable account that cannot be written
84    InvalidWritableAccount,
85
86    /// Transaction would exceed max account limit within the block
87    WouldExceedMaxAccountCostLimit,
88
89    /// Transaction would exceed account data limit within the block
90    WouldExceedAccountDataBlockLimit,
91
92    /// Transaction locked too many accounts
93    TooManyAccountLocks,
94
95    /// Address lookup table not found
96    AddressLookupTableNotFound,
97
98    /// Attempted to lookup addresses from an account owned by the wrong program
99    InvalidAddressLookupTableOwner,
100
101    /// Attempted to lookup addresses from an invalid account
102    InvalidAddressLookupTableData,
103
104    /// Address table lookup uses an invalid index
105    InvalidAddressLookupTableIndex,
106
107    /// Transaction leaves an account with a lower balance than rent-exempt minimum
108    InvalidRentPayingAccount,
109
110    /// Transaction would exceed max Vote Cost Limit
111    WouldExceedMaxVoteCostLimit,
112
113    /// Transaction would exceed total account data limit
114    WouldExceedAccountDataTotalLimit,
115
116    /// Transaction contains a duplicate instruction that is not allowed
117    DuplicateInstruction(u8),
118
119    /// Transaction results in an account with insufficient funds for rent
120    InsufficientFundsForRent {
121        account_index: u8,
122    },
123
124    /// Transaction exceeded max loaded accounts data size cap
125    MaxLoadedAccountsDataSizeExceeded,
126
127    /// LoadedAccountsDataSizeLimit set for transaction must be greater than 0.
128    InvalidLoadedAccountsDataSizeLimit,
129
130    /// Sanitized transaction differed before/after feature activiation. Needs to be resanitized.
131    ResanitizationNeeded,
132
133    /// Program execution is temporarily restricted on an account.
134    ProgramExecutionTemporarilyRestricted {
135        account_index: u8,
136    },
137
138    /// The total balance before the transaction does not equal the total balance after the transaction
139    UnbalancedTransaction,
140
141    /// Program cache hit max limit.
142    ProgramCacheHitMaxLimit,
143
144    /// Commit cancelled internally.
145    CommitCancelled,
146}
147
148impl std::error::Error for TransactionError {}
149
150impl fmt::Display for TransactionError {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        match self {
153            Self::AccountInUse
154             => f.write_str("Account in use"),
155            Self::AccountLoadedTwice
156             => f.write_str("Account loaded twice"),
157            Self::AccountNotFound
158             => f.write_str("Attempt to debit an account but found no record of a prior credit."),
159            Self::ProgramAccountNotFound
160             => f.write_str("Attempt to load a program that does not exist"),
161            Self::InsufficientFundsForFee
162             => f.write_str("Insufficient funds for fee"),
163            Self::InvalidAccountForFee
164             => f.write_str("This account may not be used to pay transaction fees"),
165            Self::AlreadyProcessed
166             => f.write_str("This transaction has already been processed"),
167            Self::BlockhashNotFound
168             => f.write_str("Blockhash not found"),
169            Self::InstructionError(idx, err) =>  write!(f, "Error processing Instruction {idx}: {err}"),
170            Self::CallChainTooDeep
171             => f.write_str("Loader call chain is too deep"),
172            Self::MissingSignatureForFee
173             => f.write_str("Transaction requires a fee but has no signature present"),
174            Self::InvalidAccountIndex
175             => f.write_str("Transaction contains an invalid account reference"),
176            Self::SignatureFailure
177             => f.write_str("Transaction did not pass signature verification"),
178            Self::InvalidProgramForExecution
179             => f.write_str("This program may not be used for executing instructions"),
180            Self::SanitizeFailure
181             => f.write_str("Transaction failed to sanitize accounts offsets correctly"),
182            Self::ClusterMaintenance
183             => f.write_str("Transactions are currently disabled due to cluster maintenance"),
184            Self::AccountBorrowOutstanding
185             => f.write_str("Transaction processing left an account with an outstanding borrowed reference"),
186            Self::WouldExceedMaxBlockCostLimit
187             => f.write_str("Transaction would exceed max Block Cost Limit"),
188            Self::UnsupportedVersion
189             => f.write_str("Transaction version is unsupported"),
190            Self::InvalidWritableAccount
191             => f.write_str("Transaction loads a writable account that cannot be written"),
192            Self::WouldExceedMaxAccountCostLimit
193             => f.write_str("Transaction would exceed max account limit within the block"),
194            Self::WouldExceedAccountDataBlockLimit
195             => f.write_str("Transaction would exceed account data limit within the block"),
196            Self::TooManyAccountLocks
197             => f.write_str("Transaction locked too many accounts"),
198            Self::AddressLookupTableNotFound
199             => f.write_str("Transaction loads an address table account that doesn't exist"),
200            Self::InvalidAddressLookupTableOwner
201             => f.write_str("Transaction loads an address table account with an invalid owner"),
202            Self::InvalidAddressLookupTableData
203             => f.write_str("Transaction loads an address table account with invalid data"),
204            Self::InvalidAddressLookupTableIndex
205             => f.write_str("Transaction address table lookup uses an invalid index"),
206            Self::InvalidRentPayingAccount
207             => f.write_str("Transaction leaves an account with a lower balance than rent-exempt minimum"),
208            Self::WouldExceedMaxVoteCostLimit
209             => f.write_str("Transaction would exceed max Vote Cost Limit"),
210            Self::WouldExceedAccountDataTotalLimit
211             => f.write_str("Transaction would exceed total account data limit"),
212            Self::DuplicateInstruction(idx) =>  write!(f, "Transaction contains a duplicate instruction ({idx}) that is not allowed"),
213            Self::InsufficientFundsForRent {
214                account_index
215            } =>  write!(f,"Transaction results in an account ({account_index}) with insufficient funds for rent"),
216            Self::MaxLoadedAccountsDataSizeExceeded
217             => f.write_str("Transaction exceeded max loaded accounts data size cap"),
218            Self::InvalidLoadedAccountsDataSizeLimit
219             => f.write_str("LoadedAccountsDataSizeLimit set for transaction must be greater than 0."),
220            Self::ResanitizationNeeded
221             => f.write_str("ResanitizationNeeded"),
222            Self::ProgramExecutionTemporarilyRestricted {
223                account_index
224            } =>  write!(f,"Execution of the program referenced by account at index {account_index} is temporarily restricted."),
225            Self::UnbalancedTransaction
226             => f.write_str("Sum of account balances before and after transaction do not match"),
227            Self::ProgramCacheHitMaxLimit
228             => f.write_str("Program cache hit max limit"),
229            Self::CommitCancelled
230             => f.write_str("CommitCancelled"),
231        }
232    }
233}
234
235impl From<SanitizeError> for TransactionError {
236    fn from(_: SanitizeError) -> Self {
237        Self::SanitizeFailure
238    }
239}
240
241#[cfg(not(target_os = "solana"))]
242impl From<SanitizeMessageError> for TransactionError {
243    fn from(err: SanitizeMessageError) -> Self {
244        match err {
245            SanitizeMessageError::AddressLoaderError(err) => Self::from(err),
246            _ => Self::SanitizeFailure,
247        }
248    }
249}
250
251#[cfg(not(target_os = "solana"))]
252#[derive(Debug, PartialEq, Eq, Clone)]
253pub enum AddressLoaderError {
254    /// Address loading from lookup tables is disabled
255    Disabled,
256
257    /// Failed to load slot hashes sysvar
258    SlotHashesSysvarNotFound,
259
260    /// Attempted to lookup addresses from a table that does not exist
261    LookupTableAccountNotFound,
262
263    /// Attempted to lookup addresses from an account owned by the wrong program
264    InvalidAccountOwner,
265
266    /// Attempted to lookup addresses from an invalid account
267    InvalidAccountData,
268
269    /// Address lookup contains an invalid index
270    InvalidLookupIndex,
271}
272
273#[cfg(not(target_os = "solana"))]
274impl std::error::Error for AddressLoaderError {}
275
276#[cfg(not(target_os = "solana"))]
277impl fmt::Display for AddressLoaderError {
278    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279        match self {
280            Self::Disabled => f.write_str("Address loading from lookup tables is disabled"),
281            Self::SlotHashesSysvarNotFound => f.write_str("Failed to load slot hashes sysvar"),
282            Self::LookupTableAccountNotFound => {
283                f.write_str("Attempted to lookup addresses from a table that does not exist")
284            }
285            Self::InvalidAccountOwner => f.write_str(
286                "Attempted to lookup addresses from an account owned by the wrong program",
287            ),
288            Self::InvalidAccountData => {
289                f.write_str("Attempted to lookup addresses from an invalid account")
290            }
291            Self::InvalidLookupIndex => f.write_str("Address lookup contains an invalid index"),
292        }
293    }
294}
295
296#[cfg(not(target_os = "solana"))]
297impl From<AddressLoaderError> for TransactionError {
298    fn from(err: AddressLoaderError) -> Self {
299        match err {
300            AddressLoaderError::Disabled => Self::UnsupportedVersion,
301            AddressLoaderError::SlotHashesSysvarNotFound => Self::AccountNotFound,
302            AddressLoaderError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound,
303            AddressLoaderError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner,
304            AddressLoaderError::InvalidAccountData => Self::InvalidAddressLookupTableData,
305            AddressLoaderError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex,
306        }
307    }
308}
309
310#[cfg(not(target_os = "solana"))]
311#[derive(PartialEq, Debug, Eq, Clone)]
312pub enum SanitizeMessageError {
313    IndexOutOfBounds,
314    ValueOutOfBounds,
315    InvalidValue,
316    AddressLoaderError(AddressLoaderError),
317}
318
319#[cfg(not(target_os = "solana"))]
320impl std::error::Error for SanitizeMessageError {
321    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
322        match self {
323            Self::IndexOutOfBounds => None,
324            Self::ValueOutOfBounds => None,
325            Self::InvalidValue => None,
326            Self::AddressLoaderError(e) => Some(e),
327        }
328    }
329}
330
331#[cfg(not(target_os = "solana"))]
332impl fmt::Display for SanitizeMessageError {
333    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334        match self {
335            Self::IndexOutOfBounds => f.write_str("index out of bounds"),
336            Self::ValueOutOfBounds => f.write_str("value out of bounds"),
337            Self::InvalidValue => f.write_str("invalid value"),
338            Self::AddressLoaderError(e) => {
339                write!(f, "{e}")
340            }
341        }
342    }
343}
344#[cfg(not(target_os = "solana"))]
345impl From<AddressLoaderError> for SanitizeMessageError {
346    fn from(source: AddressLoaderError) -> Self {
347        SanitizeMessageError::AddressLoaderError(source)
348    }
349}
350
351#[cfg(not(target_os = "solana"))]
352impl From<SanitizeError> for SanitizeMessageError {
353    fn from(err: SanitizeError) -> Self {
354        match err {
355            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
356            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
357            SanitizeError::InvalidValue => Self::InvalidValue,
358        }
359    }
360}
361
362#[cfg(not(target_os = "solana"))]
363#[derive(Debug)]
364pub enum TransportError {
365    IoError(std::io::Error),
366    TransactionError(TransactionError),
367    Custom(String),
368}
369
370#[cfg(not(target_os = "solana"))]
371impl std::error::Error for TransportError {
372    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
373        match self {
374            TransportError::IoError(e) => Some(e),
375            TransportError::TransactionError(e) => Some(e),
376            TransportError::Custom(_) => None,
377        }
378    }
379}
380
381#[cfg(not(target_os = "solana"))]
382impl fmt::Display for TransportError {
383    fn fmt(&self, f: &mut fmt::Formatter) -> ::core::fmt::Result {
384        match self {
385            Self::IoError(e) => f.write_fmt(format_args!("transport io error: {e}")),
386            Self::TransactionError(e) => {
387                f.write_fmt(format_args!("transport transaction error: {e}"))
388            }
389            Self::Custom(s) => f.write_fmt(format_args!("transport custom error: {s}")),
390        }
391    }
392}
393
394#[cfg(not(target_os = "solana"))]
395impl From<std::io::Error> for TransportError {
396    fn from(e: std::io::Error) -> Self {
397        TransportError::IoError(e)
398    }
399}
400
401#[cfg(not(target_os = "solana"))]
402impl From<TransactionError> for TransportError {
403    fn from(e: TransactionError) -> Self {
404        TransportError::TransactionError(e)
405    }
406}
407
408#[cfg(not(target_os = "solana"))]
409impl TransportError {
410    pub fn unwrap(&self) -> TransactionError {
411        if let TransportError::TransactionError(err) = self {
412            err.clone()
413        } else {
414            panic!("unexpected transport error")
415        }
416    }
417}
418
419#[cfg(not(target_os = "solana"))]
420pub type TransportResult<T> = std::result::Result<T, TransportError>;