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 {core::fmt, solana_instruction_error::InstructionError, solana_sanitize::SanitizeError};
8
9pub type TransactionResult<T> = Result<T, TransactionError>;
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 activation. 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    /// Commit cancelled internally.
142    CommitCancelled,
143}
144
145impl core::error::Error for TransactionError {}
146
147impl fmt::Display for TransactionError {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        match self {
150            Self::AccountInUse
151             => f.write_str("Account in use"),
152            Self::AccountLoadedTwice
153             => f.write_str("Account loaded twice"),
154            Self::AccountNotFound
155             => f.write_str("Attempt to debit an account but found no record of a prior credit."),
156            Self::ProgramAccountNotFound
157             => f.write_str("Attempt to load a program that does not exist"),
158            Self::InsufficientFundsForFee
159             => f.write_str("Insufficient funds for fee"),
160            Self::InvalidAccountForFee
161             => f.write_str("This account may not be used to pay transaction fees"),
162            Self::AlreadyProcessed
163             => f.write_str("This transaction has already been processed"),
164            Self::BlockhashNotFound
165             => f.write_str("Blockhash not found"),
166            Self::InstructionError(idx, err) =>  write!(f, "Error processing Instruction {idx}: {err}"),
167            Self::CallChainTooDeep
168             => f.write_str("Loader call chain is too deep"),
169            Self::MissingSignatureForFee
170             => f.write_str("Transaction requires a fee but has no signature present"),
171            Self::InvalidAccountIndex
172             => f.write_str("Transaction contains an invalid account reference"),
173            Self::SignatureFailure
174             => f.write_str("Transaction did not pass signature verification"),
175            Self::InvalidProgramForExecution
176             => f.write_str("This program may not be used for executing instructions"),
177            Self::SanitizeFailure
178             => f.write_str("Transaction failed to sanitize accounts offsets correctly"),
179            Self::ClusterMaintenance
180             => f.write_str("Transactions are currently disabled due to cluster maintenance"),
181            Self::AccountBorrowOutstanding
182             => f.write_str("Transaction processing left an account with an outstanding borrowed reference"),
183            Self::WouldExceedMaxBlockCostLimit
184             => f.write_str("Transaction would exceed max Block Cost Limit"),
185            Self::UnsupportedVersion
186             => f.write_str("Transaction version is unsupported"),
187            Self::InvalidWritableAccount
188             => f.write_str("Transaction loads a writable account that cannot be written"),
189            Self::WouldExceedMaxAccountCostLimit
190             => f.write_str("Transaction would exceed max account limit within the block"),
191            Self::WouldExceedAccountDataBlockLimit
192             => f.write_str("Transaction would exceed account data limit within the block"),
193            Self::TooManyAccountLocks
194             => f.write_str("Transaction locked too many accounts"),
195            Self::AddressLookupTableNotFound
196             => f.write_str("Transaction loads an address table account that doesn't exist"),
197            Self::InvalidAddressLookupTableOwner
198             => f.write_str("Transaction loads an address table account with an invalid owner"),
199            Self::InvalidAddressLookupTableData
200             => f.write_str("Transaction loads an address table account with invalid data"),
201            Self::InvalidAddressLookupTableIndex
202             => f.write_str("Transaction address table lookup uses an invalid index"),
203            Self::InvalidRentPayingAccount
204             => f.write_str("Transaction leaves an account with a lower balance than rent-exempt minimum"),
205            Self::WouldExceedMaxVoteCostLimit
206             => f.write_str("Transaction would exceed max Vote Cost Limit"),
207            Self::WouldExceedAccountDataTotalLimit
208             => f.write_str("Transaction would exceed total account data limit"),
209            Self::DuplicateInstruction(idx) =>  write!(f, "Transaction contains a duplicate instruction ({idx}) that is not allowed"),
210            Self::InsufficientFundsForRent {
211                account_index
212            } =>  write!(f,"Transaction results in an account ({account_index}) with insufficient funds for rent"),
213            Self::MaxLoadedAccountsDataSizeExceeded
214             => f.write_str("Transaction exceeded max loaded accounts data size cap"),
215            Self::InvalidLoadedAccountsDataSizeLimit
216             => f.write_str("LoadedAccountsDataSizeLimit set for transaction must be greater than 0."),
217            Self::ResanitizationNeeded
218             => f.write_str("ResanitizationNeeded"),
219            Self::ProgramExecutionTemporarilyRestricted {
220                account_index
221            } =>  write!(f,"Execution of the program referenced by account at index {account_index} is temporarily restricted."),
222            Self::UnbalancedTransaction
223             => f.write_str("Sum of account balances before and after transaction do not match"),
224            Self::ProgramCacheHitMaxLimit
225             => f.write_str("Program cache hit max limit"),
226            Self::CommitCancelled
227             => f.write_str("CommitCancelled"),
228        }
229    }
230}
231
232impl From<SanitizeError> for TransactionError {
233    fn from(_: SanitizeError) -> Self {
234        Self::SanitizeFailure
235    }
236}
237
238#[cfg(not(target_os = "solana"))]
239impl From<SanitizeMessageError> for TransactionError {
240    fn from(err: SanitizeMessageError) -> Self {
241        match err {
242            SanitizeMessageError::AddressLoaderError(err) => Self::from(err),
243            _ => Self::SanitizeFailure,
244        }
245    }
246}
247
248#[cfg(not(target_os = "solana"))]
249#[derive(Debug, PartialEq, Eq, Clone)]
250pub enum AddressLoaderError {
251    /// Address loading from lookup tables is disabled
252    Disabled,
253
254    /// Failed to load slot hashes sysvar
255    SlotHashesSysvarNotFound,
256
257    /// Attempted to lookup addresses from a table that does not exist
258    LookupTableAccountNotFound,
259
260    /// Attempted to lookup addresses from an account owned by the wrong program
261    InvalidAccountOwner,
262
263    /// Attempted to lookup addresses from an invalid account
264    InvalidAccountData,
265
266    /// Address lookup contains an invalid index
267    InvalidLookupIndex,
268}
269
270#[cfg(not(target_os = "solana"))]
271impl core::error::Error for AddressLoaderError {}
272
273#[cfg(not(target_os = "solana"))]
274impl fmt::Display for AddressLoaderError {
275    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
276        match self {
277            Self::Disabled => f.write_str("Address loading from lookup tables is disabled"),
278            Self::SlotHashesSysvarNotFound => f.write_str("Failed to load slot hashes sysvar"),
279            Self::LookupTableAccountNotFound => {
280                f.write_str("Attempted to lookup addresses from a table that does not exist")
281            }
282            Self::InvalidAccountOwner => f.write_str(
283                "Attempted to lookup addresses from an account owned by the wrong program",
284            ),
285            Self::InvalidAccountData => {
286                f.write_str("Attempted to lookup addresses from an invalid account")
287            }
288            Self::InvalidLookupIndex => f.write_str("Address lookup contains an invalid index"),
289        }
290    }
291}
292
293#[cfg(not(target_os = "solana"))]
294impl From<AddressLoaderError> for TransactionError {
295    fn from(err: AddressLoaderError) -> Self {
296        match err {
297            AddressLoaderError::Disabled => Self::UnsupportedVersion,
298            AddressLoaderError::SlotHashesSysvarNotFound => Self::AccountNotFound,
299            AddressLoaderError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound,
300            AddressLoaderError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner,
301            AddressLoaderError::InvalidAccountData => Self::InvalidAddressLookupTableData,
302            AddressLoaderError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex,
303        }
304    }
305}
306
307#[cfg(not(target_os = "solana"))]
308#[derive(PartialEq, Debug, Eq, Clone)]
309pub enum SanitizeMessageError {
310    IndexOutOfBounds,
311    ValueOutOfBounds,
312    InvalidValue,
313    AddressLoaderError(AddressLoaderError),
314}
315
316#[cfg(not(target_os = "solana"))]
317impl core::error::Error for SanitizeMessageError {
318    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
319        match self {
320            Self::IndexOutOfBounds => None,
321            Self::ValueOutOfBounds => None,
322            Self::InvalidValue => None,
323            Self::AddressLoaderError(e) => Some(e),
324        }
325    }
326}
327
328#[cfg(not(target_os = "solana"))]
329impl fmt::Display for SanitizeMessageError {
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        match self {
332            Self::IndexOutOfBounds => f.write_str("index out of bounds"),
333            Self::ValueOutOfBounds => f.write_str("value out of bounds"),
334            Self::InvalidValue => f.write_str("invalid value"),
335            Self::AddressLoaderError(e) => {
336                write!(f, "{e}")
337            }
338        }
339    }
340}
341#[cfg(not(target_os = "solana"))]
342impl From<AddressLoaderError> for SanitizeMessageError {
343    fn from(source: AddressLoaderError) -> Self {
344        SanitizeMessageError::AddressLoaderError(source)
345    }
346}
347
348#[cfg(not(target_os = "solana"))]
349impl From<SanitizeError> for SanitizeMessageError {
350    fn from(err: SanitizeError) -> Self {
351        match err {
352            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
353            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
354            SanitizeError::InvalidValue => Self::InvalidValue,
355        }
356    }
357}
358
359#[cfg(not(target_os = "solana"))]
360#[derive(Debug)]
361pub enum TransportError {
362    IoError(std::io::Error),
363    TransactionError(TransactionError),
364    Custom(String),
365}
366
367#[cfg(not(target_os = "solana"))]
368impl core::error::Error for TransportError {
369    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
370        match self {
371            TransportError::IoError(e) => Some(e),
372            TransportError::TransactionError(e) => Some(e),
373            TransportError::Custom(_) => None,
374        }
375    }
376}
377
378#[cfg(not(target_os = "solana"))]
379impl fmt::Display for TransportError {
380    fn fmt(&self, f: &mut fmt::Formatter) -> ::core::fmt::Result {
381        match self {
382            Self::IoError(e) => f.write_fmt(format_args!("transport io error: {e}")),
383            Self::TransactionError(e) => {
384                f.write_fmt(format_args!("transport transaction error: {e}"))
385            }
386            Self::Custom(s) => f.write_fmt(format_args!("transport custom error: {s}")),
387        }
388    }
389}
390
391#[cfg(not(target_os = "solana"))]
392impl From<std::io::Error> for TransportError {
393    fn from(e: std::io::Error) -> Self {
394        TransportError::IoError(e)
395    }
396}
397
398#[cfg(not(target_os = "solana"))]
399impl From<TransactionError> for TransportError {
400    fn from(e: TransactionError) -> Self {
401        TransportError::TransactionError(e)
402    }
403}
404
405#[cfg(not(target_os = "solana"))]
406impl TransportError {
407    pub fn unwrap(&self) -> TransactionError {
408        if let TransportError::TransactionError(err) = self {
409            err.clone()
410        } else {
411            panic!("unexpected transport error")
412        }
413    }
414}
415
416#[cfg(not(target_os = "solana"))]
417pub type TransportResult<T> = std::result::Result<T, TransportError>;