miden_client/rpc/errors/
mod.rs1use alloc::boxed::Box;
2use alloc::string::{String, ToString};
3use core::error::Error;
4use core::fmt;
5use core::num::TryFromIntError;
6
7use miden_protocol::account::AccountId;
8use miden_protocol::crypto::merkle::MerkleError;
9use miden_protocol::errors::NoteError;
10use miden_protocol::note::NoteId;
11use miden_protocol::utils::DeserializationError;
12use thiserror::Error;
13
14use super::RpcEndpoint;
15
16pub mod node;
17pub use node::EndpointError;
18
19#[derive(Debug, Error)]
23pub enum RpcError {
24 #[error("accept header validation failed")]
25 AcceptHeaderError(#[from] AcceptHeaderError),
26 #[error(
27 "unexpected update received for private account {0}; private account state should not be sent by the node"
28 )]
29 AccountUpdateForPrivateAccountReceived(AccountId),
30 #[error("failed to connect to the Miden node")]
31 ConnectionError(#[source] Box<dyn Error + Send + Sync + 'static>),
32 #[error("failed to deserialize response from the Miden node: {0}")]
33 DeserializationError(String),
34 #[error("Miden node response is missing expected field '{0}'")]
35 ExpectedDataMissing(String),
36 #[error("rpc pagination error: {0}")]
37 PaginationError(String),
38 #[error("received an invalid response from the Miden node: {0}")]
39 InvalidResponse(String),
40 #[error("grpc request failed for {endpoint}: {error_kind}{}",
41 endpoint_error.as_ref().map_or(String::new(), |e| format!(" ({e})")))]
42 RequestError {
43 endpoint: RpcEndpoint,
44 error_kind: GrpcError,
45 endpoint_error: Option<EndpointError>,
46 #[source]
47 source: Option<Box<dyn Error + Send + Sync + 'static>>,
48 },
49 #[error("note {0} was not found on the Miden node")]
50 NoteNotFound(NoteId),
51 #[error("invalid Miden node endpoint '{0}'; expected format: https://host:port")]
52 InvalidNodeEndpoint(String),
53}
54
55impl RpcError {
56 pub fn endpoint_error(&self) -> Option<&EndpointError> {
58 match self {
59 Self::RequestError { endpoint_error, .. } => endpoint_error.as_ref(),
60 _ => None,
61 }
62 }
63}
64
65impl From<DeserializationError> for RpcError {
66 fn from(err: DeserializationError) -> Self {
67 Self::DeserializationError(err.to_string())
68 }
69}
70
71impl From<NoteError> for RpcError {
72 fn from(err: NoteError) -> Self {
73 Self::DeserializationError(err.to_string())
74 }
75}
76
77impl From<RpcConversionError> for RpcError {
78 fn from(err: RpcConversionError) -> Self {
79 Self::DeserializationError(err.to_string())
80 }
81}
82
83#[derive(Debug, Error)]
87pub enum RpcConversionError {
88 #[error("failed to deserialize")]
89 DeserializationError(#[from] DeserializationError),
90 #[error(
91 "invalid field element: value is outside the valid range (0..modulus, where modulus = 2^64 - 2^32 + 1)"
92 )]
93 NotAValidFelt,
94 #[error("invalid note type in node response")]
95 NoteTypeError(#[from] NoteError),
96 #[error("merkle proof error in node response")]
97 MerkleError(#[from] MerkleError),
98 #[error("invalid field in node response: {0}")]
99 InvalidField(String),
100 #[error("integer conversion failed in node response")]
101 InvalidInt(#[from] TryFromIntError),
102 #[error("field `{field_name}` expected to be present in protobuf representation of {entity}")]
103 MissingFieldInProtobufRepresentation {
104 entity: &'static str,
105 field_name: &'static str,
106 },
107}
108
109#[derive(Debug, Error)]
114pub enum GrpcError {
115 #[error("resource not found")]
116 NotFound,
117 #[error("invalid request parameters")]
118 InvalidArgument,
119 #[error("permission denied")]
120 PermissionDenied,
121 #[error("resource already exists")]
122 AlreadyExists,
123 #[error("request was rate-limited or the node's resources are exhausted; retry after a delay")]
124 ResourceExhausted,
125 #[error("precondition failed")]
126 FailedPrecondition,
127 #[error("operation was cancelled")]
128 Cancelled,
129 #[error("request to Miden node timed out; the node may be under heavy load")]
130 DeadlineExceeded,
131 #[error("Miden node is unavailable; check that the node is running and reachable")]
132 Unavailable,
133 #[error("Miden node returned an internal error; this is likely a node-side issue")]
134 Internal,
135 #[error("the requested method is not implemented by this version of the Miden node")]
136 Unimplemented,
137 #[error(
138 "request was rejected as unauthenticated; check your credentials and connection settings"
139 )]
140 Unauthenticated,
141 #[error("operation was aborted")]
142 Aborted,
143 #[error("operation was attempted past the valid range")]
144 OutOfRange,
145 #[error("unrecoverable data loss or corruption")]
146 DataLoss,
147 #[error("unknown error: {0}")]
148 Unknown(String),
149}
150
151impl GrpcError {
152 pub fn from_code(code: i32, message: Option<String>) -> Self {
155 match code {
156 1 => Self::Cancelled,
157 2 => Self::Unknown(message.unwrap_or_default()),
158 3 => Self::InvalidArgument,
159 4 => Self::DeadlineExceeded,
160 5 => Self::NotFound,
161 6 => Self::AlreadyExists,
162 7 => Self::PermissionDenied,
163 8 => Self::ResourceExhausted,
164 9 => Self::FailedPrecondition,
165 10 => Self::Aborted,
166 11 => Self::OutOfRange,
167 12 => Self::Unimplemented,
168 13 => Self::Internal,
169 14 => Self::Unavailable,
170 15 => Self::DataLoss,
171 16 => Self::Unauthenticated,
172 _ => Self::Unknown(
173 message.unwrap_or_else(|| format!("Unknown gRPC status code: {code}")),
174 ),
175 }
176 }
177}
178
179#[derive(Debug, Error)]
187pub enum AcceptHeaderError {
188 #[error("server rejected request - please check your version and network settings ({0})")]
189 NoSupportedMediaRange(AcceptHeaderContext),
190 #[error("server rejected request - parsing error: {0}")]
191 ParsingError(String),
192}
193
194#[derive(Debug, Clone)]
196pub struct AcceptHeaderContext {
197 pub client_version: String,
198 pub genesis_commitment: String,
199}
200
201impl AcceptHeaderContext {
202 pub fn unknown() -> Self {
203 Self {
204 client_version: "unknown".to_string(),
205 genesis_commitment: "unknown".to_string(),
206 }
207 }
208}
209
210impl fmt::Display for AcceptHeaderContext {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(
213 f,
214 "client version: {}, genesis commitment: {}",
215 self.client_version, self.genesis_commitment
216 )
217 }
218}
219
220impl AcceptHeaderError {
221 pub fn try_from_message_with_context(
223 message: &str,
224 context: AcceptHeaderContext,
225 ) -> Option<Self> {
226 if message.contains(
228 "server does not support any of the specified application/vnd.miden content types",
229 ) {
230 return Some(Self::NoSupportedMediaRange(context));
231 }
232 if message.contains("genesis value failed to parse")
233 || message.contains("version value failed to parse")
234 {
235 return Some(Self::ParsingError(message.to_string()));
236 }
237 None
238 }
239}