1use 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::serde::DeserializationError;
12use thiserror::Error;
13
14use super::RpcEndpoint;
15
16pub mod node;
17pub use node::{AddTransactionError, 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("failed to seal transaction inputs for submission: {0}")]
52 TransactionInputsSealingFailed(String),
53 #[error("the transaction encryption key served by the node was rejected: {0}")]
54 TransactionEncryptionKeyRejected(String),
55 #[error("invalid Miden node endpoint '{0}'; expected format: https://host:port")]
56 InvalidNodeEndpoint(String),
57}
58
59impl RpcError {
60 pub fn endpoint_error(&self) -> Option<&EndpointError> {
62 match self {
63 Self::RequestError { endpoint_error, .. } => endpoint_error.as_ref(),
64 _ => None,
65 }
66 }
67
68 pub fn is_stale_transaction_encryption_key(&self) -> bool {
71 matches!(
72 self,
73 Self::RequestError {
74 endpoint: RpcEndpoint::SubmitProvenTx | RpcEndpoint::SubmitProvenBatch,
75 error_kind: GrpcError::FailedPrecondition,
76 ..
77 }
78 )
79 }
80}
81
82impl From<DeserializationError> for RpcError {
83 fn from(err: DeserializationError) -> Self {
84 Self::DeserializationError(err.to_string())
85 }
86}
87
88impl From<NoteError> for RpcError {
89 fn from(err: NoteError) -> Self {
90 Self::DeserializationError(err.to_string())
91 }
92}
93
94impl From<RpcConversionError> for RpcError {
95 fn from(err: RpcConversionError) -> Self {
96 Self::DeserializationError(err.to_string())
97 }
98}
99
100#[derive(Debug, Error)]
104pub enum RpcConversionError {
105 #[error("failed to deserialize")]
106 DeserializationError(#[from] DeserializationError),
107 #[error(
108 "invalid field element: value is outside the valid range (0..modulus, where modulus = 2^64 - 2^32 + 1)"
109 )]
110 NotAValidFelt,
111 #[error("invalid note type in node response")]
112 NoteTypeError(#[from] NoteError),
113 #[error("merkle proof error in node response")]
114 MerkleError(#[from] MerkleError),
115 #[error("invalid field in node response: {0}")]
116 InvalidField(String),
117 #[error("integer conversion failed in node response")]
118 InvalidInt(#[from] TryFromIntError),
119 #[error("field `{field_name}` expected to be present in protobuf representation of {entity}")]
120 MissingFieldInProtobufRepresentation {
121 entity: &'static str,
122 field_name: &'static str,
123 },
124}
125
126#[derive(Debug, Error)]
131pub enum GrpcError {
132 #[error("resource not found")]
133 NotFound,
134 #[error("invalid request parameters")]
135 InvalidArgument,
136 #[error("permission denied")]
137 PermissionDenied,
138 #[error("resource already exists")]
139 AlreadyExists,
140 #[error("request was rate-limited or the node's resources are exhausted; retry after a delay")]
141 ResourceExhausted,
142 #[error("precondition failed")]
143 FailedPrecondition,
144 #[error("operation was cancelled")]
145 Cancelled,
146 #[error("request to Miden node timed out; the node may be under heavy load")]
147 DeadlineExceeded,
148 #[error("Miden node is unavailable; check that the node is running and reachable")]
149 Unavailable,
150 #[error("Miden node returned an internal error; this is likely a node-side issue")]
151 Internal,
152 #[error("the requested method is not implemented by this version of the Miden node")]
153 Unimplemented,
154 #[error(
155 "request was rejected as unauthenticated; check your credentials and connection settings"
156 )]
157 Unauthenticated,
158 #[error("operation was aborted")]
159 Aborted,
160 #[error("operation was attempted past the valid range")]
161 OutOfRange,
162 #[error("unrecoverable data loss or corruption")]
163 DataLoss,
164 #[error("unknown error: {0}")]
165 Unknown(String),
166}
167
168impl GrpcError {
169 pub fn from_code(code: i32, message: Option<String>) -> Self {
172 match code {
173 1 => Self::Cancelled,
174 2 => Self::Unknown(message.unwrap_or_default()),
175 3 => Self::InvalidArgument,
176 4 => Self::DeadlineExceeded,
177 5 => Self::NotFound,
178 6 => Self::AlreadyExists,
179 7 => Self::PermissionDenied,
180 8 => Self::ResourceExhausted,
181 9 => Self::FailedPrecondition,
182 10 => Self::Aborted,
183 11 => Self::OutOfRange,
184 12 => Self::Unimplemented,
185 13 => Self::Internal,
186 14 => Self::Unavailable,
187 15 => Self::DataLoss,
188 16 => Self::Unauthenticated,
189 _ => Self::Unknown(
190 message.unwrap_or_else(|| format!("Unknown gRPC status code: {code}")),
191 ),
192 }
193 }
194}
195
196#[derive(Debug, Error)]
204pub enum AcceptHeaderError {
205 #[error("server rejected request - please check your version and network settings ({0})")]
206 NoSupportedMediaRange(AcceptHeaderContext),
207 #[error("server rejected request - parsing error: {0}")]
208 ParsingError(String),
209}
210
211#[derive(Debug, Clone)]
213pub struct AcceptHeaderContext {
214 pub client_version: String,
215 pub genesis_commitment: String,
216}
217
218impl AcceptHeaderContext {
219 pub fn unknown() -> Self {
220 Self {
221 client_version: "unknown".to_string(),
222 genesis_commitment: "unknown".to_string(),
223 }
224 }
225}
226
227impl fmt::Display for AcceptHeaderContext {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 write!(
230 f,
231 "client version: {}, genesis commitment: {}",
232 self.client_version, self.genesis_commitment
233 )
234 }
235}
236
237impl AcceptHeaderError {
238 pub fn try_from_message_with_context(
240 message: &str,
241 context: AcceptHeaderContext,
242 ) -> Option<Self> {
243 if message.contains(
245 "server does not support any of the specified application/vnd.miden content types",
246 ) {
247 return Some(Self::NoSupportedMediaRange(context));
248 }
249 if message.contains("genesis value failed to parse")
250 || message.contains("version value failed to parse")
251 {
252 return Some(Self::ParsingError(message.to_string()));
253 }
254 None
255 }
256}