fedimint_api_client/api/
error.rs1use std::collections::BTreeMap;
2use std::fmt::{self, Debug, Display};
3use std::time::Duration;
4
5use fedimint_connectors::error::ServerError;
6use fedimint_core::PeerId;
7use fedimint_core::config::FederationId;
8use fedimint_core::core::ModuleInstanceId;
9use fedimint_core::encoding::DecodeError;
10use fedimint_core::fmt_utils::AbbreviateJson;
11use fedimint_core::util::FmtCompact as _;
12#[cfg(feature = "uniffi")]
13use fedimint_core::util::ffi::UniffiError;
14use fedimint_logging::LOG_CLIENT_NET_API;
15use serde::Serialize;
16use thiserror::Error;
17use tracing::{trace, warn};
18
19#[derive(Debug, Error)]
21#[non_exhaustive]
22pub enum FederationGeneralError {
23 #[error("Admin peer id not set")]
26 AdminPeerIdNotSet,
27
28 #[error("{message}")]
30 ThresholdFailed { message: String },
31
32 #[error("{message}")]
34 UnexpectedResponse { message: String },
35
36 #[error("Failed to decode a peer's response")]
38 Decode(#[from] DecodeError),
39
40 #[error("Invalid signature")]
42 InvalidSignature,
43}
44
45#[derive(Debug, Error)]
49pub struct FederationError {
50 pub method: String,
51 pub params: serde_json::Value,
52 pub general: Option<FederationGeneralError>,
57 pub peer_errors: BTreeMap<PeerId, ServerError>,
58}
59
60#[cfg(feature = "uniffi")]
61impl From<FederationError> for UniffiError {
62 fn from(e: FederationError) -> Self {
63 Self::General(e.to_string())
64 }
65}
66
67impl Display for FederationError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.write_str("Federation rpc error { ")?;
70 f.write_fmt(format_args!("method => {}, ", self.method))?;
71 if let Some(general) = self.general.as_ref() {
72 f.write_fmt(format_args!(
73 "params => {:?}, ",
74 AbbreviateJson(&self.params)
75 ))?;
76 f.write_fmt(format_args!("general => {}, ", general.fmt_compact()))?;
77 if !self.peer_errors.is_empty() {
78 f.write_str(", ")?;
79 }
80 }
81 for (i, (peer, e)) in self.peer_errors.iter().enumerate() {
82 f.write_fmt(format_args!("{peer} => {e:#}"))?;
83 if i != self.peer_errors.len() - 1 {
84 f.write_str(", ")?;
85 }
86 }
87 f.write_str(" }")?;
88 Ok(())
89 }
90}
91
92impl FederationError {
93 pub fn general(
94 method: impl Into<String>,
95 params: impl Serialize,
96 e: FederationGeneralError,
97 ) -> FederationError {
98 FederationError {
99 method: method.into(),
100 params: serde_json::to_value(params).unwrap_or_default(),
101 general: Some(e),
102 peer_errors: BTreeMap::default(),
103 }
104 }
105
106 pub(crate) fn peer_errors(
107 method: impl Into<String>,
108 params: impl Serialize,
109 peer_errors: BTreeMap<PeerId, ServerError>,
110 ) -> Self {
111 Self {
112 method: method.into(),
113 params: serde_json::to_value(params).unwrap_or_default(),
114 general: None,
115 peer_errors,
116 }
117 }
118
119 pub fn new_one_peer(
120 peer_id: PeerId,
121 method: impl Into<String>,
122 params: impl Serialize,
123 error: ServerError,
124 ) -> Self {
125 Self {
126 method: method.into(),
127 params: serde_json::to_value(params).expect("Serialization of valid params won't fail"),
128 general: None,
129 peer_errors: [(peer_id, error)].into_iter().collect(),
130 }
131 }
132
133 pub fn report_if_unusual(&self, context: &str) {
135 if let Some(error) = self.general.as_ref() {
136 warn!(target: LOG_CLIENT_NET_API, err = %error.fmt_compact(), %context, "General FederationError");
138 }
139 for (peer_id, e) in &self.peer_errors {
140 e.report_if_unusual(*peer_id, context);
141 }
142 }
143
144 pub fn get_general_error(&self) -> Option<&FederationGeneralError> {
146 self.general.as_ref()
147 }
148
149 pub fn get_peer_errors(&self) -> impl Iterator<Item = (PeerId, &ServerError)> {
151 self.peer_errors.iter().map(|(peer, error)| (*peer, error))
152 }
153
154 pub fn any_peer_error_method_not_found(&self) -> bool {
155 self.peer_errors
156 .values()
157 .any(|peer_err| matches!(peer_err, ServerError::InvalidRpcId(_)))
158 }
159}
160
161#[derive(Debug, Error)]
162#[non_exhaustive]
163pub enum OutputOutcomeError {
164 #[error("Failed to decode the output outcome")]
166 ResponseDeserialization(#[from] DecodeError),
167 #[error("Output outcome of module instance {module_instance_id} is not a {expected_type}")]
169 WrongOutcomeType {
170 module_instance_id: ModuleInstanceId,
171 expected_type: &'static str,
172 },
173 #[error("Federation error")]
175 Federation(#[from] FederationError),
176 #[error("Transaction rejected: {0}")]
178 Rejected(String),
179 #[error("Invalid output index {out_idx}, larger than {outputs_num} in the transaction")]
181 InvalidVout { out_idx: u64, outputs_num: usize },
182 #[error("Timeout reached after waiting {}s", .0.as_secs())]
184 Timeout(Duration),
185}
186
187impl OutputOutcomeError {
188 pub fn report_if_important(&self) {
189 let important = match self {
190 OutputOutcomeError::Federation(e) => {
191 e.report_if_unusual("OutputOutcome");
192 return;
193 }
194 OutputOutcomeError::WrongOutcomeType { .. }
195 | OutputOutcomeError::InvalidVout { .. }
196 | OutputOutcomeError::ResponseDeserialization(_) => true,
197 OutputOutcomeError::Rejected(_) | OutputOutcomeError::Timeout(_) => false,
198 };
199
200 trace!(target: LOG_CLIENT_NET_API, error = %self.fmt_compact(), "OutputOutcomeError");
201
202 if important {
203 warn!(
204 target: LOG_CLIENT_NET_API,
205 error = %self.fmt_compact(),
206 "Uncommon OutputOutcomeError"
207 );
208 }
209 }
210
211 pub fn is_rejected(&self) -> bool {
213 matches!(
214 self,
215 OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
216 )
217 }
218}
219
220#[derive(Debug, Error)]
222#[non_exhaustive]
223pub enum ClientConfigDownloadError {
224 #[error("Failed to request the client config")]
226 Federation(#[from] FederationError),
227
228 #[error("Obtained client config has federation id {found}, expected {expected}")]
231 FederationIdMismatch {
232 expected: FederationId,
233 found: FederationId,
234 },
235}