fedimint_gateway_server/
error.rs

1use std::fmt::Display;
2
3use axum::response::{IntoResponse, Response};
4use fedimint_core::config::{FederationId, FederationIdPrefix};
5use fedimint_core::crit;
6use fedimint_core::envs::is_env_var_set;
7use fedimint_core::fmt_utils::OptStacktrace;
8use fedimint_gw_client::pay::OutgoingPaymentError;
9use fedimint_lightning::LightningRpcError;
10use fedimint_logging::LOG_GATEWAY;
11use reqwest::StatusCode;
12use thiserror::Error;
13
14use crate::envs::FM_DEBUG_GATEWAY_ENV;
15
16/// Errors that unauthenticated endpoints can encounter. For privacy reasons,
17/// the error messages are intended to be redacted before returning to the
18/// client.
19#[derive(Debug, Error)]
20pub enum PublicGatewayError {
21    #[error("Lightning rpc error: {}", .0)]
22    Lightning(#[from] LightningRpcError),
23    #[error("LNv1 error: {:?}", .0)]
24    LNv1(#[from] LNv1Error),
25    #[error("LNv2 error: {:?}", .0)]
26    LNv2(#[from] LNv2Error),
27    #[error("{}", .0)]
28    FederationNotConnected(#[from] FederationNotConnected),
29    #[error("Failed to receive ecash: {failure_reason}")]
30    ReceiveEcashError { failure_reason: String },
31}
32
33impl IntoResponse for PublicGatewayError {
34    fn into_response(self) -> Response {
35        // For privacy reasons, we do not return too many details about the failure of
36        // the request back to the client to prevent malicious clients from
37        // deducing state about the gateway/lightning node.
38        crit!(target: LOG_GATEWAY, "{self}");
39        let (error_message, status_code) = match &self {
40            PublicGatewayError::FederationNotConnected(e) => {
41                (e.to_string(), StatusCode::BAD_REQUEST)
42            }
43            PublicGatewayError::ReceiveEcashError { .. } => (
44                "Failed to receive ecash".to_string(),
45                StatusCode::INTERNAL_SERVER_ERROR,
46            ),
47            PublicGatewayError::Lightning(_) => (
48                "Lightning Network operation failed".to_string(),
49                StatusCode::INTERNAL_SERVER_ERROR,
50            ),
51            PublicGatewayError::LNv1(_) => (
52                "LNv1 operation failed, please contact gateway operator".to_string(),
53                StatusCode::INTERNAL_SERVER_ERROR,
54            ),
55            PublicGatewayError::LNv2(_) => (
56                "LNv2 operation failed, please contact gateway operator".to_string(),
57                StatusCode::INTERNAL_SERVER_ERROR,
58            ),
59        };
60
61        let error_message = if is_env_var_set(FM_DEBUG_GATEWAY_ENV) {
62            self.to_string()
63        } else {
64            error_message
65        };
66
67        Response::builder()
68            .status(status_code)
69            .body(error_message.into())
70            .expect("Failed to create Response")
71    }
72}
73
74/// Errors that authenticated endpoints can encounter. Full error message and
75/// error details are returned to the admin client for debugging purposes.
76#[derive(Debug, Error)]
77pub enum AdminGatewayError {
78    #[error("Failed to create a federation client: {}", OptStacktrace(.0))]
79    ClientCreationError(anyhow::Error),
80    #[error("Failed to remove a federation client: {}", OptStacktrace(.0))]
81    ClientRemovalError(String),
82    #[error("There was an error with the Gateway's mnemonic: {}", OptStacktrace(.0))]
83    MnemonicError(anyhow::Error),
84    #[error("Unexpected Error: {}", OptStacktrace(.0))]
85    Unexpected(#[from] anyhow::Error),
86    #[error("{}", .0)]
87    FederationNotConnected(#[from] FederationNotConnected),
88    #[error("Error configuring the gateway: {}", OptStacktrace(.0))]
89    GatewayConfigurationError(String),
90    #[error("Lightning error: {}", OptStacktrace(.0))]
91    Lightning(#[from] LightningRpcError),
92    #[error("Error registering federation {federation_id}")]
93    RegistrationError { federation_id: FederationId },
94    #[error("Error withdrawing funds onchain: {failure_reason}")]
95    WithdrawError { failure_reason: String },
96}
97
98impl IntoResponse for AdminGatewayError {
99    // For admin errors, always pass along the full error message for debugging
100    // purposes
101    fn into_response(self) -> Response {
102        crit!(target: LOG_GATEWAY, "{self}");
103        Response::builder()
104            .status(StatusCode::INTERNAL_SERVER_ERROR)
105            .body(self.to_string().into())
106            .expect("Failed to create Response")
107    }
108}
109
110/// Errors that can occur during the LNv1 protocol. LNv1 errors are public and
111/// the error messages should be redacted for privacy reasons.
112#[derive(Debug, Error)]
113pub enum LNv1Error {
114    #[error("Incoming payment error: {}", OptStacktrace(.0))]
115    IncomingPayment(String),
116    #[error(
117        "Outgoing Contract Error Reason: {message} Stack: {}",
118        OptStacktrace(error)
119    )]
120    OutgoingContract {
121        error: Box<OutgoingPaymentError>,
122        message: String,
123    },
124    #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
125    OutgoingPayment(#[from] anyhow::Error),
126}
127
128/// Errors that can occur during the LNv2 protocol. LNv2 errors are public and
129/// the error messages should be redacted for privacy reasons.
130#[derive(Debug, Error)]
131pub enum LNv2Error {
132    #[error("Incoming Payment Error: {}", .0)]
133    IncomingPayment(String),
134    #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
135    OutgoingPayment(#[from] anyhow::Error),
136}
137
138/// Public error that indicates the requested federation is not connected to
139/// this gateway.
140#[derive(Debug, Error)]
141pub struct FederationNotConnected {
142    pub federation_id_prefix: FederationIdPrefix,
143}
144
145impl Display for FederationNotConnected {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(
148            f,
149            "No federation available for prefix {}",
150            self.federation_id_prefix
151        )
152    }
153}