solana_rpc_client_api/
custom_error.rs

1//! Implementation defined RPC server errors
2use {
3    crate::response::RpcSimulateTransactionResult,
4    jsonrpc_core::{Error, ErrorCode},
5    solana_clock::Slot,
6    solana_transaction_status_client_types::EncodeError,
7    thiserror::Error,
8};
9
10// Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
11pub const JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: i64 = -32001;
12pub const JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: i64 = -32002;
13pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: i64 = -32003;
14pub const JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: i64 = -32004;
15pub const JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: i64 = -32005;
16pub const JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: i64 = -32006;
17pub const JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: i64 = -32007;
18pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008;
19pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009;
20pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010;
21pub const JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: i64 = -32011;
22pub const JSON_RPC_SCAN_ERROR: i64 = -32012;
23pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013;
24pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014;
25pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015;
26pub const JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: i64 = -32016;
27pub const JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE: i64 = -32017;
28pub const JSON_RPC_SERVER_ERROR_SLOT_NOT_EPOCH_BOUNDARY: i64 = -32018;
29pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_UNREACHABLE: i64 = -32019;
30
31#[derive(Error, Debug)]
32pub enum RpcCustomError {
33    #[error("BlockCleanedUp")]
34    BlockCleanedUp {
35        slot: Slot,
36        first_available_block: Slot,
37    },
38    #[error("SendTransactionPreflightFailure")]
39    SendTransactionPreflightFailure {
40        message: String,
41        result: RpcSimulateTransactionResult,
42    },
43    #[error("TransactionSignatureVerificationFailure")]
44    TransactionSignatureVerificationFailure,
45    #[error("BlockNotAvailable")]
46    BlockNotAvailable { slot: Slot },
47    #[error("NodeUnhealthy")]
48    NodeUnhealthy { num_slots_behind: Option<Slot> },
49    #[error("TransactionPrecompileVerificationFailure")]
50    TransactionPrecompileVerificationFailure(solana_transaction_error::TransactionError),
51    #[error("SlotSkipped")]
52    SlotSkipped { slot: Slot },
53    #[error("NoSnapshot")]
54    NoSnapshot,
55    #[error("LongTermStorageSlotSkipped")]
56    LongTermStorageSlotSkipped { slot: Slot },
57    #[error("KeyExcludedFromSecondaryIndex")]
58    KeyExcludedFromSecondaryIndex { index_key: String },
59    #[error("TransactionHistoryNotAvailable")]
60    TransactionHistoryNotAvailable,
61    #[error("ScanError")]
62    ScanError { message: String },
63    #[error("TransactionSignatureLenMismatch")]
64    TransactionSignatureLenMismatch,
65    #[error("BlockStatusNotAvailableYet")]
66    BlockStatusNotAvailableYet { slot: Slot },
67    #[error("UnsupportedTransactionVersion")]
68    UnsupportedTransactionVersion(u8),
69    #[error("MinContextSlotNotReached")]
70    MinContextSlotNotReached { context_slot: Slot },
71    #[error("EpochRewardsPeriodActive")]
72    EpochRewardsPeriodActive {
73        slot: Slot,
74        current_block_height: u64,
75        rewards_complete_block_height: u64,
76    },
77    #[error("SlotNotEpochBoundary")]
78    SlotNotEpochBoundary { slot: Slot },
79    #[error("LongTermStorageUnreachable")]
80    LongTermStorageUnreachable,
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct NodeUnhealthyErrorData {
86    pub num_slots_behind: Option<Slot>,
87}
88
89#[derive(Debug, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct MinContextSlotNotReachedErrorData {
92    pub context_slot: Slot,
93}
94
95#[derive(Debug, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct EpochRewardsPeriodActiveErrorData {
98    pub current_block_height: u64,
99    pub rewards_complete_block_height: u64,
100}
101
102impl From<EncodeError> for RpcCustomError {
103    fn from(err: EncodeError) -> Self {
104        match err {
105            EncodeError::UnsupportedTransactionVersion(version) => {
106                Self::UnsupportedTransactionVersion(version)
107            }
108        }
109    }
110}
111
112impl From<RpcCustomError> for Error {
113    fn from(e: RpcCustomError) -> Self {
114        match e {
115            RpcCustomError::BlockCleanedUp {
116                slot,
117                first_available_block,
118            } => Self {
119                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP),
120                message: format!(
121                    "Block {slot} cleaned up, does not exist on node. First available block: \
122                     {first_available_block}",
123                ),
124                data: None,
125            },
126            RpcCustomError::SendTransactionPreflightFailure { message, result } => Self {
127                code: ErrorCode::ServerError(
128                    JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
129                ),
130                message,
131                data: Some(serde_json::json!(result)),
132            },
133            RpcCustomError::TransactionSignatureVerificationFailure => Self {
134                code: ErrorCode::ServerError(
135                    JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE,
136                ),
137                message: "Transaction signature verification failure".to_string(),
138                data: None,
139            },
140            RpcCustomError::BlockNotAvailable { slot } => Self {
141                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE),
142                message: format!("Block not available for slot {slot}"),
143                data: None,
144            },
145            RpcCustomError::NodeUnhealthy { num_slots_behind } => Self {
146                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY),
147                message: if let Some(num_slots_behind) = num_slots_behind {
148                    format!("Node is behind by {num_slots_behind} slots")
149                } else {
150                    "Node is unhealthy".to_string()
151                },
152                data: Some(serde_json::json!(NodeUnhealthyErrorData {
153                    num_slots_behind
154                })),
155            },
156            RpcCustomError::TransactionPrecompileVerificationFailure(e) => Self {
157                code: ErrorCode::ServerError(
158                    JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE,
159                ),
160                message: format!("Transaction precompile verification failure {e:?}"),
161                data: None,
162            },
163            RpcCustomError::SlotSkipped { slot } => Self {
164                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_SLOT_SKIPPED),
165                message: format!(
166                    "Slot {slot} was skipped, or missing due to ledger jump to recent snapshot"
167                ),
168                data: None,
169            },
170            RpcCustomError::NoSnapshot => Self {
171                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_NO_SNAPSHOT),
172                message: "No snapshot".to_string(),
173                data: None,
174            },
175            RpcCustomError::LongTermStorageSlotSkipped { slot } => Self {
176                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED),
177                message: format!("Slot {slot} was skipped, or missing in long-term storage"),
178                data: None,
179            },
180            RpcCustomError::KeyExcludedFromSecondaryIndex { index_key } => Self {
181                code: ErrorCode::ServerError(
182                    JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX,
183                ),
184                message: format!(
185                    "{index_key} excluded from account secondary indexes; this RPC method \
186                     unavailable for key"
187                ),
188                data: None,
189            },
190            RpcCustomError::TransactionHistoryNotAvailable => Self {
191                code: ErrorCode::ServerError(
192                    JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE,
193                ),
194                message: "Transaction history is not available from this node".to_string(),
195                data: None,
196            },
197            RpcCustomError::ScanError { message } => Self {
198                code: ErrorCode::ServerError(JSON_RPC_SCAN_ERROR),
199                message,
200                data: None,
201            },
202            RpcCustomError::TransactionSignatureLenMismatch => Self {
203                code: ErrorCode::ServerError(
204                    JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH,
205                ),
206                message: "Transaction signature length mismatch".to_string(),
207                data: None,
208            },
209            RpcCustomError::BlockStatusNotAvailableYet { slot } => Self {
210                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET),
211                message: format!("Block status not yet available for slot {slot}"),
212                data: None,
213            },
214            RpcCustomError::UnsupportedTransactionVersion(version) => Self {
215                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION),
216                message: format!(
217                    "Transaction version ({version}) is not supported by the requesting client. \
218                     Please try the request again with the following configuration parameter: \
219                     \"maxSupportedTransactionVersion\": {version}"
220                ),
221                data: None,
222            },
223            RpcCustomError::MinContextSlotNotReached { context_slot } => Self {
224                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED),
225                message: "Minimum context slot has not been reached".to_string(),
226                data: Some(serde_json::json!(MinContextSlotNotReachedErrorData {
227                    context_slot,
228                })),
229            },
230            RpcCustomError::EpochRewardsPeriodActive {
231                slot,
232                current_block_height,
233                rewards_complete_block_height,
234            } => Self {
235                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE),
236                message: format!("Epoch rewards period still active at slot {slot}"),
237                data: Some(serde_json::json!(EpochRewardsPeriodActiveErrorData {
238                    current_block_height,
239                    rewards_complete_block_height,
240                })),
241            },
242            RpcCustomError::SlotNotEpochBoundary { slot } => Self {
243                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_SLOT_NOT_EPOCH_BOUNDARY),
244                message: format!(
245                    "Rewards cannot be found because slot {slot} is not the epoch boundary. This \
246                     may be due to gap in the queried node's local ledger or long-term storage"
247                ),
248                data: None,
249            },
250            RpcCustomError::LongTermStorageUnreachable => Self {
251                code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_UNREACHABLE),
252                message: "Failed to query long-term storage; please try again".to_string(),
253                data: None,
254            },
255        }
256    }
257}