use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display};
use std::time::Duration;
use fedimint_connectors::error::ServerError;
use fedimint_core::PeerId;
use fedimint_core::config::FederationId;
use fedimint_core::core::ModuleInstanceId;
use fedimint_core::encoding::DecodeError;
use fedimint_core::fmt_utils::AbbreviateJson;
use fedimint_core::util::FmtCompact as _;
use fedimint_logging::LOG_CLIENT_NET_API;
use serde::Serialize;
use thiserror::Error;
use tracing::{trace, warn};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FederationGeneralError {
#[error("Admin peer id not set")]
AdminPeerIdNotSet,
#[error("{message}")]
ThresholdFailed { message: String },
#[error("{message}")]
UnexpectedResponse { message: String },
#[error("Failed to decode a peer's response")]
Decode(#[from] DecodeError),
#[error("Invalid signature")]
InvalidSignature,
}
#[derive(Debug, Error)]
pub struct FederationError {
pub method: String,
pub params: serde_json::Value,
pub general: Option<FederationGeneralError>,
pub peer_errors: BTreeMap<PeerId, ServerError>,
}
impl Display for FederationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Federation rpc error { ")?;
f.write_fmt(format_args!("method => {}, ", self.method))?;
if let Some(general) = self.general.as_ref() {
f.write_fmt(format_args!(
"params => {:?}, ",
AbbreviateJson(&self.params)
))?;
f.write_fmt(format_args!("general => {}, ", general.fmt_compact()))?;
if !self.peer_errors.is_empty() {
f.write_str(", ")?;
}
}
for (i, (peer, e)) in self.peer_errors.iter().enumerate() {
f.write_fmt(format_args!("{peer} => {e:#}"))?;
if i != self.peer_errors.len() - 1 {
f.write_str(", ")?;
}
}
f.write_str(" }")?;
Ok(())
}
}
impl FederationError {
pub fn general(
method: impl Into<String>,
params: impl Serialize,
e: FederationGeneralError,
) -> FederationError {
FederationError {
method: method.into(),
params: serde_json::to_value(params).unwrap_or_default(),
general: Some(e),
peer_errors: BTreeMap::default(),
}
}
pub(crate) fn peer_errors(
method: impl Into<String>,
params: impl Serialize,
peer_errors: BTreeMap<PeerId, ServerError>,
) -> Self {
Self {
method: method.into(),
params: serde_json::to_value(params).unwrap_or_default(),
general: None,
peer_errors,
}
}
pub fn new_one_peer(
peer_id: PeerId,
method: impl Into<String>,
params: impl Serialize,
error: ServerError,
) -> Self {
Self {
method: method.into(),
params: serde_json::to_value(params).expect("Serialization of valid params won't fail"),
general: None,
peer_errors: [(peer_id, error)].into_iter().collect(),
}
}
pub fn report_if_unusual(&self, context: &str) {
if let Some(error) = self.general.as_ref() {
warn!(target: LOG_CLIENT_NET_API, err = %error.fmt_compact(), %context, "General FederationError");
}
for (peer_id, e) in &self.peer_errors {
e.report_if_unusual(*peer_id, context);
}
}
pub fn get_general_error(&self) -> Option<&FederationGeneralError> {
self.general.as_ref()
}
pub fn get_peer_errors(&self) -> impl Iterator<Item = (PeerId, &ServerError)> {
self.peer_errors.iter().map(|(peer, error)| (*peer, error))
}
pub fn any_peer_error_method_not_found(&self) -> bool {
self.peer_errors
.values()
.any(|peer_err| matches!(peer_err, ServerError::InvalidRpcId(_)))
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum OutputOutcomeError {
#[error("Failed to decode the output outcome")]
ResponseDeserialization(#[from] DecodeError),
#[error("Output outcome of module instance {module_instance_id} is not a {expected_type}")]
WrongOutcomeType {
module_instance_id: ModuleInstanceId,
expected_type: &'static str,
},
#[error("Federation error")]
Federation(#[from] FederationError),
#[error("Transaction rejected: {0}")]
Rejected(String),
#[error("Invalid output index {out_idx}, larger than {outputs_num} in the transaction")]
InvalidVout { out_idx: u64, outputs_num: usize },
#[error("Timeout reached after waiting {}s", .0.as_secs())]
Timeout(Duration),
}
impl OutputOutcomeError {
pub fn report_if_important(&self) {
let important = match self {
OutputOutcomeError::Federation(e) => {
e.report_if_unusual("OutputOutcome");
return;
}
OutputOutcomeError::WrongOutcomeType { .. }
| OutputOutcomeError::InvalidVout { .. }
| OutputOutcomeError::ResponseDeserialization(_) => true,
OutputOutcomeError::Rejected(_) | OutputOutcomeError::Timeout(_) => false,
};
trace!(target: LOG_CLIENT_NET_API, error = %self.fmt_compact(), "OutputOutcomeError");
if important {
warn!(
target: LOG_CLIENT_NET_API,
error = %self.fmt_compact(),
"Uncommon OutputOutcomeError"
);
}
}
pub fn is_rejected(&self) -> bool {
matches!(
self,
OutputOutcomeError::Rejected(_) | OutputOutcomeError::InvalidVout { .. }
)
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ClientConfigDownloadError {
#[error("Failed to request the client config")]
Federation(#[from] FederationError),
#[error("Obtained client config has federation id {found}, expected {expected}")]
FederationIdMismatch {
expected: FederationId,
found: FederationId,
},
}