1use {
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
10pub 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}