miden_client/rpc/
errors.rs1use alloc::boxed::Box;
2use alloc::string::{String, ToString};
3use core::error::Error;
4use core::num::TryFromIntError;
5
6use miden_protocol::account::AccountId;
7use miden_protocol::crypto::merkle::MerkleError;
8use miden_protocol::errors::NoteError;
9use miden_protocol::note::NoteId;
10use miden_protocol::utils::DeserializationError;
11use thiserror::Error;
12
13use super::NodeRpcClientEndpoint;
14
15#[derive(Debug, Error)]
19pub enum RpcError {
20 #[error("accept header validation failed")]
21 AcceptHeaderError(#[from] AcceptHeaderError),
22 #[error(
23 "unexpected update received for private account {0}; private account state should not be sent by the node"
24 )]
25 AccountUpdateForPrivateAccountReceived(AccountId),
26 #[error("failed to connect to the Miden node")]
27 ConnectionError(#[source] Box<dyn Error + Send + Sync + 'static>),
28 #[error("failed to deserialize response from the Miden node: {0}")]
29 DeserializationError(String),
30 #[error("Miden node response is missing expected field '{0}'")]
31 ExpectedDataMissing(String),
32 #[error("received an invalid response from the Miden node: {0}")]
33 InvalidResponse(String),
34 #[error("request to Miden node failed ({endpoint}): {error_kind}")]
35 GrpcError {
36 endpoint: NodeRpcClientEndpoint,
37 error_kind: GrpcError,
38 #[source]
39 source: Option<Box<dyn Error + Send + Sync + 'static>>,
40 },
41 #[error("note {0} was not found on the Miden node")]
42 NoteNotFound(NoteId),
43 #[error("invalid Miden node endpoint '{0}'; expected format: https://host:port")]
44 InvalidNodeEndpoint(String),
45}
46
47impl From<DeserializationError> for RpcError {
48 fn from(err: DeserializationError) -> Self {
49 Self::DeserializationError(err.to_string())
50 }
51}
52
53impl From<NoteError> for RpcError {
54 fn from(err: NoteError) -> Self {
55 Self::DeserializationError(err.to_string())
56 }
57}
58
59impl From<RpcConversionError> for RpcError {
60 fn from(err: RpcConversionError) -> Self {
61 Self::DeserializationError(err.to_string())
62 }
63}
64
65#[derive(Debug, Error)]
69pub enum RpcConversionError {
70 #[error("failed to deserialize")]
71 DeserializationError(#[from] DeserializationError),
72 #[error(
73 "invalid field element: value is outside the valid range (0..modulus, where modulus = 2^64 - 2^32 + 1)"
74 )]
75 NotAValidFelt,
76 #[error("invalid note type in node response")]
77 NoteTypeError(#[from] NoteError),
78 #[error("merkle proof error in node response")]
79 MerkleError(#[from] MerkleError),
80 #[error("invalid field in node response: {0}")]
81 InvalidField(String),
82 #[error("integer conversion failed in node response")]
83 InvalidInt(#[from] TryFromIntError),
84 #[error("field `{field_name}` expected to be present in protobuf representation of {entity}")]
85 MissingFieldInProtobufRepresentation {
86 entity: &'static str,
87 field_name: &'static str,
88 },
89}
90
91#[derive(Debug, Error)]
96pub enum GrpcError {
97 #[error("resource not found")]
98 NotFound,
99 #[error("invalid request parameters")]
100 InvalidArgument,
101 #[error("permission denied")]
102 PermissionDenied,
103 #[error("resource already exists")]
104 AlreadyExists,
105 #[error("request was rate-limited or the node's resources are exhausted; retry after a delay")]
106 ResourceExhausted,
107 #[error("precondition failed")]
108 FailedPrecondition,
109 #[error("operation was cancelled")]
110 Cancelled,
111 #[error("request to Miden node timed out; the node may be under heavy load")]
112 DeadlineExceeded,
113 #[error("Miden node is unavailable; check that the node is running and reachable")]
114 Unavailable,
115 #[error("Miden node returned an internal error; this is likely a node-side issue")]
116 Internal,
117 #[error("the requested method is not implemented by this version of the Miden node")]
118 Unimplemented,
119 #[error(
120 "request was rejected as unauthenticated; check your credentials and connection settings"
121 )]
122 Unauthenticated,
123 #[error("operation was aborted")]
124 Aborted,
125 #[error("operation was attempted past the valid range")]
126 OutOfRange,
127 #[error("unrecoverable data loss or corruption")]
128 DataLoss,
129 #[error("unknown error: {0}")]
130 Unknown(String),
131}
132
133impl GrpcError {
134 pub fn from_code(code: i32, message: Option<String>) -> Self {
137 match code {
138 1 => Self::Cancelled,
139 2 => Self::Unknown(message.unwrap_or_default()),
140 3 => Self::InvalidArgument,
141 4 => Self::DeadlineExceeded,
142 5 => Self::NotFound,
143 6 => Self::AlreadyExists,
144 7 => Self::PermissionDenied,
145 8 => Self::ResourceExhausted,
146 9 => Self::FailedPrecondition,
147 10 => Self::Aborted,
148 11 => Self::OutOfRange,
149 12 => Self::Unimplemented,
150 13 => Self::Internal,
151 14 => Self::Unavailable,
152 15 => Self::DataLoss,
153 16 => Self::Unauthenticated,
154 _ => Self::Unknown(
155 message.unwrap_or_else(|| format!("Unknown gRPC status code: {code}")),
156 ),
157 }
158 }
159}
160
161#[derive(Debug, Error)]
169pub enum AcceptHeaderError {
170 #[error("server rejected request - please check your version and network settings")]
171 NoSupportedMediaRange,
172 #[error("server rejected request - parsing error: {0}")]
173 ParsingError(String),
174}
175
176impl AcceptHeaderError {
177 pub fn try_from_message(message: &str) -> Option<Self> {
179 if message.contains(
181 "server does not support any of the specified application/vnd.miden content types",
182 ) {
183 return Some(Self::NoSupportedMediaRange);
184 }
185 if message.contains("genesis value failed to parse")
186 || message.contains("version value failed to parse")
187 {
188 return Some(Self::ParsingError(message.to_string()));
189 }
190 None
191 }
192}