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