fedimint_api_client/api/
error.rs1use std::collections::BTreeMap;
2use std::fmt::{self, Debug, Display};
3use std::time::Duration;
4
5use fedimint_core::fmt_utils::AbbreviateJson;
6use fedimint_core::PeerId;
7use fedimint_logging::LOG_CLIENT_NET_API;
8use jsonrpsee_core::client::Error as JsonRpcClientError;
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)]
17pub enum PeerError {
18 #[error("Response deserialization error: {0}")]
19 ResponseDeserialization(anyhow::Error),
20 #[error("Invalid peer id: {peer_id}")]
21 InvalidPeerId { peer_id: PeerId },
22 #[error("Rpc error: {0}")]
23 Rpc(#[from] JsonRpcClientError),
24 #[error("Invalid response: {0}")]
25 InvalidResponse(String),
26}
27
28impl PeerError {
29 pub fn report_if_important(&self, peer_id: PeerId) {
35 let important = match self {
36 PeerError::ResponseDeserialization(_)
37 | PeerError::InvalidPeerId { .. }
38 | PeerError::InvalidResponse(_) => true,
39 PeerError::Rpc(rpc_e) => match rpc_e {
40 JsonRpcClientError::Transport(_) | JsonRpcClientError::RequestTimeout => false,
42 JsonRpcClientError::RestartNeeded(_)
43 | JsonRpcClientError::Call(_)
44 | JsonRpcClientError::ParseError(_)
45 | JsonRpcClientError::InvalidSubscriptionId
46 | JsonRpcClientError::InvalidRequestId(_)
47 | JsonRpcClientError::Custom(_)
48 | JsonRpcClientError::HttpNotImplemented
49 | JsonRpcClientError::EmptyBatchRequest(_)
50 | JsonRpcClientError::RegisterMethod(_) => true,
51 },
52 };
53
54 trace!(target: LOG_CLIENT_NET_API, error = %self, "PeerError");
55
56 if important {
57 warn!(target: LOG_CLIENT_NET_API, error = %self, %peer_id, "Unusual PeerError");
58 }
59 }
60}
61
62#[derive(Debug, Error)]
66pub struct FederationError {
67 pub method: String,
68 pub params: serde_json::Value,
69 pub general: Option<anyhow::Error>,
70 pub peers: BTreeMap<PeerId, PeerError>,
71}
72
73impl Display for FederationError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str("Federation rpc error {")?;
76 if let Some(general) = self.general.as_ref() {
77 f.write_fmt(format_args!("method => {}, ", self.method))?;
78 f.write_fmt(format_args!(
79 "params => {:?}, ",
80 AbbreviateJson(&self.params)
81 ))?;
82 f.write_fmt(format_args!("general => {general}, "))?;
83 if !self.peers.is_empty() {
84 f.write_str(", ")?;
85 }
86 }
87 for (i, (peer, e)) in self.peers.iter().enumerate() {
88 f.write_fmt(format_args!("{peer} => {e})"))?;
89 if i == self.peers.len() - 1 {
90 f.write_str(", ")?;
91 }
92 }
93 f.write_str("}")?;
94 Ok(())
95 }
96}
97
98impl FederationError {
99 pub fn general(
100 method: impl Into<String>,
101 params: impl Serialize,
102 e: impl Into<anyhow::Error>,
103 ) -> FederationError {
104 FederationError {
105 method: method.into(),
106 params: serde_json::to_value(params).unwrap_or_default(),
107 general: Some(e.into()),
108 peers: BTreeMap::default(),
109 }
110 }
111
112 pub fn new_one_peer(
113 peer_id: PeerId,
114 method: impl Into<String>,
115 params: impl Serialize,
116 error: PeerError,
117 ) -> Self {
118 Self {
119 method: method.into(),
120 params: serde_json::to_value(params).expect("Serialization of valid params won't fail"),
121 general: None,
122 peers: [(peer_id, error)].into_iter().collect(),
123 }
124 }
125
126 pub fn report_if_important(&self) {
128 if let Some(error) = self.general.as_ref() {
129 warn!(target: LOG_CLIENT_NET_API, %error, "General FederationError");
130 }
131 for (peer_id, e) in &self.peers {
132 e.report_if_important(*peer_id);
133 }
134 }
135
136 pub fn get_general_error(&self) -> Option<&anyhow::Error> {
138 self.general.as_ref()
139 }
140
141 pub fn get_peer_errors(&self) -> impl Iterator<Item = (PeerId, &PeerError)> {
143 self.peers.iter().map(|(peer, error)| (*peer, error))
144 }
145}
146
147#[derive(Debug, Error)]
148pub enum OutputOutcomeError {
149 #[error("Response deserialization error: {0}")]
150 ResponseDeserialization(anyhow::Error),
151 #[error("Federation error: {0}")]
152 Federation(#[from] FederationError),
153 #[error("Core error: {0}")]
154 Core(#[from] anyhow::Error),
155 #[error("Transaction rejected: {0}")]
156 Rejected(String),
157 #[error("Invalid output index {out_idx}, larger than {outputs_num} in the transaction")]
158 InvalidVout { out_idx: u64, outputs_num: usize },
159 #[error("Timeout reached after waiting {}s", .0.as_secs())]
160 Timeout(Duration),
161}
162
163impl OutputOutcomeError {
164 pub fn report_if_important(&self) {
165 let important = match self {
166 OutputOutcomeError::Federation(e) => {
167 e.report_if_important();
168 return;
169 }
170 OutputOutcomeError::Core(_)
171 | OutputOutcomeError::InvalidVout { .. }
172 | OutputOutcomeError::ResponseDeserialization(_) => true,
173 OutputOutcomeError::Rejected(_) | OutputOutcomeError::Timeout(_) => false,
174 };
175
176 trace!(target: LOG_CLIENT_NET_API, error = %self, "OutputOutcomeError");
177
178 if important {
179 warn!(target: LOG_CLIENT_NET_API, error = %self, "Uncommon OutputOutcomeError");
180 }
181 }
182
183 pub fn is_rejected(&self) -> bool {
185 matches!(
186 self,
187 OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
188 )
189 }
190}