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::fmt_utils::AbbreviateJson;
8use fedimint_core::util::FmtCompactAnyhow as _;
9#[cfg(feature = "uniffi")]
10use fedimint_core::util::ffi::UniffiError;
11use fedimint_logging::LOG_CLIENT_NET_API;
12use serde::Serialize;
13use thiserror::Error;
14use tracing::{trace, warn};
15
16#[derive(Debug, Error)]
20pub struct FederationError {
21 pub method: String,
22 pub params: serde_json::Value,
23 pub general: Option<anyhow::Error>,
28 pub peer_errors: BTreeMap<PeerId, ServerError>,
29}
30
31#[cfg(feature = "uniffi")]
32impl From<FederationError> for UniffiError {
33 fn from(e: FederationError) -> Self {
34 Self::General(e.to_string())
35 }
36}
37
38impl Display for FederationError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.write_str("Federation rpc error { ")?;
41 f.write_fmt(format_args!("method => {}, ", self.method))?;
42 if let Some(general) = self.general.as_ref() {
43 f.write_fmt(format_args!(
44 "params => {:?}, ",
45 AbbreviateJson(&self.params)
46 ))?;
47 f.write_fmt(format_args!("general => {general}, "))?;
48 if !self.peer_errors.is_empty() {
49 f.write_str(", ")?;
50 }
51 }
52 for (i, (peer, e)) in self.peer_errors.iter().enumerate() {
53 f.write_fmt(format_args!("{peer} => {e:#}"))?;
54 if i != self.peer_errors.len() - 1 {
55 f.write_str(", ")?;
56 }
57 }
58 f.write_str(" }")?;
59 Ok(())
60 }
61}
62
63impl FederationError {
64 pub fn general(
65 method: impl Into<String>,
66 params: impl Serialize,
67 e: impl Into<anyhow::Error>,
68 ) -> FederationError {
69 FederationError {
70 method: method.into(),
71 params: serde_json::to_value(params).unwrap_or_default(),
72 general: Some(e.into()),
73 peer_errors: BTreeMap::default(),
74 }
75 }
76
77 pub(crate) fn peer_errors(
78 method: impl Into<String>,
79 params: impl Serialize,
80 peer_errors: BTreeMap<PeerId, ServerError>,
81 ) -> Self {
82 Self {
83 method: method.into(),
84 params: serde_json::to_value(params).unwrap_or_default(),
85 general: None,
86 peer_errors,
87 }
88 }
89
90 pub fn new_one_peer(
91 peer_id: PeerId,
92 method: impl Into<String>,
93 params: impl Serialize,
94 error: ServerError,
95 ) -> Self {
96 Self {
97 method: method.into(),
98 params: serde_json::to_value(params).expect("Serialization of valid params won't fail"),
99 general: None,
100 peer_errors: [(peer_id, error)].into_iter().collect(),
101 }
102 }
103
104 pub fn report_if_unusual(&self, context: &str) {
106 if let Some(error) = self.general.as_ref() {
107 warn!(target: LOG_CLIENT_NET_API, err = %error.fmt_compact_anyhow(), %context, "General FederationError");
109 }
110 for (peer_id, e) in &self.peer_errors {
111 e.report_if_unusual(*peer_id, context);
112 }
113 }
114
115 pub fn get_general_error(&self) -> Option<&anyhow::Error> {
117 self.general.as_ref()
118 }
119
120 pub fn get_peer_errors(&self) -> impl Iterator<Item = (PeerId, &ServerError)> {
122 self.peer_errors.iter().map(|(peer, error)| (*peer, error))
123 }
124
125 pub fn any_peer_error_method_not_found(&self) -> bool {
126 self.peer_errors
127 .values()
128 .any(|peer_err| matches!(peer_err, ServerError::InvalidRpcId(_)))
129 }
130}
131
132#[derive(Debug, Error)]
133pub enum OutputOutcomeError {
134 #[error("Response deserialization error: {0}")]
135 ResponseDeserialization(anyhow::Error),
136 #[error("Federation error: {0}")]
137 Federation(#[from] FederationError),
138 #[error("Core error: {0}")]
139 Core(#[from] anyhow::Error),
140 #[error("Transaction rejected: {0}")]
141 Rejected(String),
142 #[error("Invalid output index {out_idx}, larger than {outputs_num} in the transaction")]
143 InvalidVout { out_idx: u64, outputs_num: usize },
144 #[error("Timeout reached after waiting {}s", .0.as_secs())]
145 Timeout(Duration),
146}
147
148impl OutputOutcomeError {
149 pub fn report_if_important(&self) {
150 let important = match self {
151 OutputOutcomeError::Federation(e) => {
152 e.report_if_unusual("OutputOutcome");
153 return;
154 }
155 OutputOutcomeError::Core(_)
156 | OutputOutcomeError::InvalidVout { .. }
157 | OutputOutcomeError::ResponseDeserialization(_) => true,
158 OutputOutcomeError::Rejected(_) | OutputOutcomeError::Timeout(_) => false,
159 };
160
161 trace!(target: LOG_CLIENT_NET_API, error = %self, "OutputOutcomeError");
162
163 if important {
164 warn!(target: LOG_CLIENT_NET_API, error = %self, "Uncommon OutputOutcomeError");
165 }
166 }
167
168 pub fn is_rejected(&self) -> bool {
170 matches!(
171 self,
172 OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
173 )
174 }
175}