Skip to main content

fedimint_gw_client/
error.rs

1//! Error types of the gateway's LNv1 client module.
2//!
3//! The error types of this module's public operations live here; the payment
4//! errors its state machines persist stay in [`crate::pay`].
5
6use fedimint_client_module::{AddStateMachinesError, TransactionSubmitError};
7use fedimint_core::core::OperationId;
8use fedimint_core::db::{AutocommitError, DatabaseError};
9use fedimint_lightning::LightningRpcError;
10use fedimint_ln_client::incoming::IncomingSmError;
11use fedimint_ln_common::contracts::ContractId;
12use thiserror::Error;
13
14use crate::UnsafeHtlcExpiry;
15
16/// A failure to handle an HTLC the gateway intercepted.
17///
18/// The gateway buys the payment's preimage from the federation by funding the
19/// incoming contract that the recipient offered. A replay of an HTLC circuit
20/// the gateway already handles is not a failure.
21#[derive(Debug, Error)]
22#[non_exhaustive]
23pub enum HandleInterceptedHtlcError {
24    /// The Lightning node could not report its current block height, which
25    /// the HTLC's expiry is checked against.
26    #[error("The Lightning node's block height could not be read")]
27    BlockHeight(#[source] LightningRpcError),
28
29    /// The HTLC expires too soon for the gateway to settle it safely.
30    #[error("The HTLC expires too soon to be settled safely")]
31    UnsafeExpiry(#[from] UnsafeHtlcExpiry),
32
33    /// The federation's offer for this payment could not be turned into an
34    /// incoming contract: it did not arrive in time, violates the fee
35    /// policy, does not match the HTLC, already has a funded contract, or
36    /// the federation could not be asked.
37    #[error("The incoming contract could not be created")]
38    IncomingContract(#[from] IncomingSmError),
39
40    /// The operation id derived while funding disagrees with the one derived
41    /// from the payment hash. This is a bug in this module; it is reported
42    /// instead of panicking so that the caller can fail the HTLC back.
43    #[error(
44        "Operation id derivation must match: {} != {}",
45        .derived.fmt_short(),
46        .expected.fmt_short()
47    )]
48    OperationIdMismatch {
49        /// The id derived from the payment hash.
50        expected: OperationId,
51        /// The id the funding step derived.
52        derived: OperationId,
53    },
54
55    /// The transaction funding the incoming contract could not be built or
56    /// submitted.
57    #[error("The funding transaction could not be submitted")]
58    Transaction(#[from] TransactionSubmitError),
59}
60
61/// A failure to fund the incoming contract of a direct swap.
62///
63/// A direct swap pays an invoice issued in this federation on behalf of a
64/// client of another federation served by the same gateway, by funding the
65/// invoice's incoming contract here, so the payment never touches the
66/// Lightning network. Joining a swap that is already under way, or declining
67/// to start one, is not a failure.
68#[derive(Debug, Error)]
69#[non_exhaustive]
70pub enum HandleDirectSwapError {
71    /// The federation's offer for this payment could not be turned into an
72    /// incoming contract: it did not arrive in time, violates the fee
73    /// policy, does not match the swap, already has a funded contract, or
74    /// the federation could not be asked.
75    #[error("The incoming contract could not be created")]
76    IncomingContract(#[from] IncomingSmError),
77
78    /// The operation id derived while funding disagrees with the one derived
79    /// from the payment hash. This is a bug in this module.
80    #[error(
81        "Operation id derivation must match: {} != {}",
82        .derived.fmt_short(),
83        .expected.fmt_short()
84    )]
85    OperationIdMismatch {
86        /// The id derived from the payment hash.
87        expected: OperationId,
88        /// The id the funding step derived.
89        derived: OperationId,
90    },
91
92    /// The transaction funding the incoming contract could not be built or
93    /// submitted.
94    #[error("The funding transaction could not be submitted")]
95    Transaction(#[from] TransactionSubmitError),
96
97    /// Recording the swap in the database kept failing.
98    #[error("Database error")]
99    Database(#[from] DatabaseError),
100}
101
102impl From<AutocommitError<HandleDirectSwapError>> for HandleDirectSwapError {
103    fn from(e: AutocommitError<Self>) -> Self {
104        match e {
105            AutocommitError::ClosureError { error, .. } => error,
106            AutocommitError::CommitFailed { last_error, .. } => Self::Database(last_error),
107        }
108    }
109}
110
111/// A failure to start paying an invoice on behalf of a federation client.
112///
113/// These are the refusals that happen before the payment's state machine
114/// starts. Once it runs, its outcome is reported through
115/// [`crate::GatewayClientModule::gateway_subscribe_ln_pay`].
116#[derive(Debug, Error)]
117#[non_exhaustive]
118pub enum GatewayPayInvoiceError {
119    /// The invoice carries no amount, so there is nothing to pay.
120    #[error("Invoice is missing amount")]
121    MissingInvoiceAmount,
122
123    /// The invoice was pruned, and the gateway cannot pay a pruned invoice.
124    #[error("The gateway cannot pay the pruned invoice")]
125    PrunedInvoiceRejected(#[source] GatewayClientV1Error),
126
127    /// A payment of this contract is already under way, and the request does
128    /// not carry the authentication it was started with.
129    #[error("Not authorized to receive the preimage for contract {contract_id}")]
130    Unauthorized {
131        /// The contract the request asked to pay.
132        contract_id: ContractId,
133    },
134
135    /// The payment's state machine could not be started.
136    #[error("Failed to add the payment's state machines")]
137    StateMachines(#[source] AddStateMachinesError),
138
139    /// Recording the payment in the database kept failing.
140    #[error("Database error")]
141    Database(#[from] DatabaseError),
142}
143
144impl From<AutocommitError<GatewayPayInvoiceError>> for GatewayPayInvoiceError {
145    fn from(e: AutocommitError<Self>) -> Self {
146        match e {
147            AutocommitError::ClosureError { error, .. } => error,
148            AutocommitError::CommitFailed { last_error, .. } => Self::Database(last_error),
149        }
150    }
151}
152
153/// A failure reported by the gateway behind [`crate::IGatewayClientV1`].
154///
155/// The trait is implemented by the gateway, not by this module, so the causes
156/// are the gateway's own. This type carries them unchanged: its `Display` and
157/// its `source()` are the cause's.
158#[derive(Debug, Error)]
159#[error(transparent)]
160pub struct GatewayClientV1Error(Box<dyn std::error::Error + Send + Sync>);
161
162impl GatewayClientV1Error {
163    /// Wraps a failure of the gateway's [`crate::IGatewayClientV1`]
164    /// implementation, which may be any error value or a plain message.
165    pub fn new<E>(source: E) -> Self
166    where
167        E: Into<Box<dyn std::error::Error + Send + Sync>>,
168    {
169        Self(source.into())
170    }
171}