1use std::collections::BTreeMap;
2use std::fmt::{self, Debug, Display};
3use std::time::Duration;
4
5use fedimint_core::PeerId;
6use fedimint_core::fmt_utils::AbbreviateJson;
7use fedimint_core::util::FmtCompactAnyhow as _;
8use fedimint_logging::LOG_CLIENT_NET_API;
9#[cfg(target_family = "wasm")]
10use jsonrpsee_wasm_client::{Client as WsClient, WasmClientBuilder as WsClientBuilder};
11use serde::Serialize;
12use thiserror::Error;
13use tracing::{error, trace, warn};
14
15#[derive(Debug, Error)]
17#[non_exhaustive]
18pub enum PeerError {
19 #[error("Response deserialization error: {0}")]
22 ResponseDeserialization(anyhow::Error),
23
24 #[error("Invalid peer id: {peer_id}")]
26 InvalidPeerId { peer_id: PeerId },
27
28 #[error("Invalid endpoint")]
30 InvalidEndpoint(anyhow::Error),
31
32 #[error("Connection failed: {0}")]
34 Connection(anyhow::Error),
35
36 #[error("Transport error: {0}")]
38 Transport(anyhow::Error),
39
40 #[error("Invalid rpc id")]
46 InvalidRpcId(anyhow::Error),
47
48 #[error("Invalid request")]
51 InvalidRequest(anyhow::Error),
52
53 #[error("Invalid response: {0}")]
55 InvalidResponse(anyhow::Error),
56
57 #[error("Unspecified server error")]
59 ServerError(anyhow::Error),
60
61 #[error("Unspecified server error")]
66 ConditionFailed(anyhow::Error),
67
68 #[error("Unspecified internal client")]
73 InternalClientError(anyhow::Error),
74}
75
76impl PeerError {
77 pub fn is_unusual(&self) -> bool {
78 match self {
79 PeerError::ResponseDeserialization(_)
80 | PeerError::InvalidPeerId { .. }
81 | PeerError::InvalidResponse(_)
82 | PeerError::InvalidRpcId(_)
83 | PeerError::InvalidRequest(_)
84 | PeerError::InternalClientError(_)
85 | PeerError::InvalidEndpoint(_)
86 | PeerError::ServerError(_) => true,
87 PeerError::Connection(_) | PeerError::Transport(_) | PeerError::ConditionFailed(_) => {
88 false
89 }
90 }
91 }
92 pub fn report_if_unusual(&self, peer_id: PeerId, context: &str) {
98 let unusual = self.is_unusual();
99
100 trace!(target: LOG_CLIENT_NET_API, error = %self, %context, "PeerError");
101
102 if unusual {
103 warn!(target: LOG_CLIENT_NET_API, error = %self,%context, %peer_id, "Unusual PeerError");
104 }
105 }
106}
107
108#[derive(Debug, Error)]
112pub struct FederationError {
113 pub method: String,
114 pub params: serde_json::Value,
115 pub general: Option<anyhow::Error>,
120 pub peer_errors: BTreeMap<PeerId, PeerError>,
121}
122
123impl Display for FederationError {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 f.write_str("Federation rpc error { ")?;
126 f.write_fmt(format_args!("method => {}, ", self.method))?;
127 if let Some(general) = self.general.as_ref() {
128 f.write_fmt(format_args!(
129 "params => {:?}, ",
130 AbbreviateJson(&self.params)
131 ))?;
132 f.write_fmt(format_args!("general => {general}, "))?;
133 if !self.peer_errors.is_empty() {
134 f.write_str(", ")?;
135 }
136 }
137 for (i, (peer, e)) in self.peer_errors.iter().enumerate() {
138 f.write_fmt(format_args!("{peer} => {e:#}"))?;
139 if i != self.peer_errors.len() - 1 {
140 f.write_str(", ")?;
141 }
142 }
143 f.write_str(" }")?;
144 Ok(())
145 }
146}
147
148impl FederationError {
149 pub fn general(
150 method: impl Into<String>,
151 params: impl Serialize,
152 e: impl Into<anyhow::Error>,
153 ) -> FederationError {
154 FederationError {
155 method: method.into(),
156 params: serde_json::to_value(params).unwrap_or_default(),
157 general: Some(e.into()),
158 peer_errors: BTreeMap::default(),
159 }
160 }
161
162 pub(crate) fn peer_errors(
163 method: impl Into<String>,
164 params: impl Serialize,
165 peer_errors: BTreeMap<PeerId, PeerError>,
166 ) -> Self {
167 Self {
168 method: method.into(),
169 params: serde_json::to_value(params).unwrap_or_default(),
170 general: None,
171 peer_errors,
172 }
173 }
174
175 pub fn new_one_peer(
176 peer_id: PeerId,
177 method: impl Into<String>,
178 params: impl Serialize,
179 error: PeerError,
180 ) -> Self {
181 Self {
182 method: method.into(),
183 params: serde_json::to_value(params).expect("Serialization of valid params won't fail"),
184 general: None,
185 peer_errors: [(peer_id, error)].into_iter().collect(),
186 }
187 }
188
189 pub fn report_if_unusual(&self, context: &str) {
191 if let Some(error) = self.general.as_ref() {
192 warn!(target: LOG_CLIENT_NET_API, err = %error.fmt_compact_anyhow(), %context, "General FederationError");
194 }
195 for (peer_id, e) in &self.peer_errors {
196 e.report_if_unusual(*peer_id, context);
197 }
198 }
199
200 pub fn get_general_error(&self) -> Option<&anyhow::Error> {
202 self.general.as_ref()
203 }
204
205 pub fn get_peer_errors(&self) -> impl Iterator<Item = (PeerId, &PeerError)> {
207 self.peer_errors.iter().map(|(peer, error)| (*peer, error))
208 }
209
210 pub fn any_peer_error_method_not_found(&self) -> bool {
211 self.peer_errors
212 .values()
213 .any(|peer_err| matches!(peer_err, PeerError::InvalidRpcId(_)))
214 }
215}
216
217#[derive(Debug, Error)]
218pub enum OutputOutcomeError {
219 #[error("Response deserialization error: {0}")]
220 ResponseDeserialization(anyhow::Error),
221 #[error("Federation error: {0}")]
222 Federation(#[from] FederationError),
223 #[error("Core error: {0}")]
224 Core(#[from] anyhow::Error),
225 #[error("Transaction rejected: {0}")]
226 Rejected(String),
227 #[error("Invalid output index {out_idx}, larger than {outputs_num} in the transaction")]
228 InvalidVout { out_idx: u64, outputs_num: usize },
229 #[error("Timeout reached after waiting {}s", .0.as_secs())]
230 Timeout(Duration),
231}
232
233impl OutputOutcomeError {
234 pub fn report_if_important(&self) {
235 let important = match self {
236 OutputOutcomeError::Federation(e) => {
237 e.report_if_unusual("OutputOutcome");
238 return;
239 }
240 OutputOutcomeError::Core(_)
241 | OutputOutcomeError::InvalidVout { .. }
242 | OutputOutcomeError::ResponseDeserialization(_) => true,
243 OutputOutcomeError::Rejected(_) | OutputOutcomeError::Timeout(_) => false,
244 };
245
246 trace!(target: LOG_CLIENT_NET_API, error = %self, "OutputOutcomeError");
247
248 if important {
249 warn!(target: LOG_CLIENT_NET_API, error = %self, "Uncommon OutputOutcomeError");
250 }
251 }
252
253 pub fn is_rejected(&self) -> bool {
255 matches!(
256 self,
257 OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
258 )
259 }
260}