Skip to main content

fedimint_api_client/api/
error.rs

1use 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/// An API request error when calling an entire federation
17///
18/// Generally all Federation errors are retryable.
19#[derive(Debug, Error)]
20pub struct FederationError {
21    pub method: String,
22    pub params: serde_json::Value,
23    /// Higher-level general error
24    ///
25    /// The `general` error should be Some, when the error is not simply peers
26    /// responding with enough errors, but something more global.
27    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    /// Report any errors
105    pub fn report_if_unusual(&self, context: &str) {
106        if let Some(error) = self.general.as_ref() {
107            // Any general federation errors are unusual
108            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    /// Get the general error if any.
116    pub fn get_general_error(&self) -> Option<&anyhow::Error> {
117        self.general.as_ref()
118    }
119
120    /// Get errors from different peers.
121    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    /// Was the transaction rejected (which is final)
169    pub fn is_rejected(&self) -> bool {
170        matches!(
171            self,
172            OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
173        )
174    }
175}