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