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}