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