ln_gateway/
error.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use std::fmt::Display;

use axum::response::{IntoResponse, Response};
use fedimint_core::config::{FederationId, FederationIdPrefix};
use fedimint_core::fmt_utils::OptStacktrace;
use reqwest::StatusCode;
use thiserror::Error;
use tracing::error;

use crate::lightning::LightningRpcError;
use crate::state_machine::pay::OutgoingPaymentError;

/// Errors that unauthenticated endpoints can encounter. For privacy reasons,
/// the error messages are intended to be redacted before returning to the
/// client.
#[derive(Debug, Error)]
pub enum PublicGatewayError {
    #[error("Lightning rpc error: {}", .0)]
    Lightning(#[from] LightningRpcError),
    #[error("LNv1 error: {:?}", .0)]
    LNv1(#[from] LNv1Error),
    #[error("LNv2 error: {:?}", .0)]
    LNv2(#[from] LNv2Error),
    #[error("{}", .0)]
    FederationNotConnected(#[from] FederationNotConnected),
    #[error("Failed to receive ecash: {failure_reason}")]
    ReceiveEcashError { failure_reason: String },
}

impl IntoResponse for PublicGatewayError {
    fn into_response(self) -> Response {
        // For privacy reasons, we do not return too many details about the failure of
        // the request back to the client to prevent malicious clients from
        // deducing state about the gateway/lightning node.
        error!("{self}");
        let (error_message, status_code) = match self {
            PublicGatewayError::FederationNotConnected(e) => {
                (e.to_string(), StatusCode::BAD_REQUEST)
            }
            PublicGatewayError::ReceiveEcashError { .. } => (
                "Failed to receive ecash".to_string(),
                StatusCode::INTERNAL_SERVER_ERROR,
            ),
            PublicGatewayError::Lightning(_) => (
                "Lightning Network operation failed".to_string(),
                StatusCode::INTERNAL_SERVER_ERROR,
            ),
            PublicGatewayError::LNv1(_) => (
                "LNv1 operation failed, please contact gateway operator".to_string(),
                StatusCode::INTERNAL_SERVER_ERROR,
            ),
            PublicGatewayError::LNv2(_) => (
                "LNv2 operation failed, please contact gateway operator".to_string(),
                StatusCode::INTERNAL_SERVER_ERROR,
            ),
        };

        Response::builder()
            .status(status_code)
            .body(error_message.into())
            .expect("Failed to create Response")
    }
}

/// Errors that authenticated endpoints can encounter. Full error message and
/// error details are returned to the admin client for debugging purposes.
#[derive(Debug, Error)]
pub enum AdminGatewayError {
    #[error("Failed to create a federation client: {}", OptStacktrace(.0))]
    ClientCreationError(anyhow::Error),
    #[error("Failed to remove a federation client: {}", OptStacktrace(.0))]
    ClientRemovalError(String),
    #[error("There was an error with the Gateway's mnemonic: {}", OptStacktrace(.0))]
    MnemonicError(anyhow::Error),
    #[error("Unexpected Error: {}", OptStacktrace(.0))]
    Unexpected(#[from] anyhow::Error),
    #[error("{}", .0)]
    FederationNotConnected(#[from] FederationNotConnected),
    #[error("Error configuring the gateway: {}", OptStacktrace(.0))]
    GatewayConfigurationError(String),
    #[error("Lightning error: {}", OptStacktrace(.0))]
    Lightning(#[from] LightningRpcError),
    #[error("Error registering federation {federation_id}")]
    RegistrationError { federation_id: FederationId },
    #[error("Error withdrawing funds onchain: {failure_reason}")]
    WithdrawError { failure_reason: String },
}

impl IntoResponse for AdminGatewayError {
    // For admin errors, always pass along the full error message for debugging
    // purposes
    fn into_response(self) -> Response {
        error!("{self}");
        Response::builder()
            .status(StatusCode::INTERNAL_SERVER_ERROR)
            .body(self.to_string().into())
            .expect("Failed to create Response")
    }
}

/// Errors that can occur during the LNv1 protocol. LNv1 errors are public and
/// the error messages should be redacted for privacy reasons.
#[derive(Debug, Error)]
pub enum LNv1Error {
    #[error("Incoming payment error: {}", OptStacktrace(.0))]
    IncomingPayment(String),
    #[error(
        "Outgoing Contract Error Reason: {message} Stack: {}",
        OptStacktrace(error)
    )]
    OutgoingContract {
        error: Box<OutgoingPaymentError>,
        message: String,
    },
    #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
    OutgoingPayment(#[from] anyhow::Error),
}

/// Errors that can occur during the LNv2 protocol. LNv2 errors are public and
/// the error messages should be redacted for privacy reasons.
#[derive(Debug, Error)]
pub enum LNv2Error {
    #[error("Incoming Payment Error: {}", .0)]
    IncomingPayment(String),
    #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
    OutgoingPayment(#[from] anyhow::Error),
}

/// Public error that indicates the requested federation is not connected to
/// this gateway.
#[derive(Debug, Error)]
pub struct FederationNotConnected {
    pub federation_id_prefix: FederationIdPrefix,
}

impl Display for FederationNotConnected {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "No federation available for prefix {}",
            self.federation_id_prefix
        )
    }
}