Skip to main content

solana_transaction_error/
lib.rs

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