1use std::fmt;
2
3use fedimint_client::DynGlobalClientContext;
4use fedimint_client_module::sm::{ClientSMDatabaseTransaction, State, StateTransition};
5use fedimint_core::core::OperationId;
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_lightning::{InterceptPaymentResponse, LightningRpcError, PaymentAction, Preimage};
8use fedimint_lnv2_common::contracts::PaymentImage;
9
10use super::FinalReceiveState;
11use super::events::CompleteLightningPaymentSucceeded;
12use crate::GatewayClientContextV2;
13
14#[cfg_attr(doc, aquamarine::aquamarine)]
15#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
33pub struct CompleteStateMachine {
34 pub common: CompleteSMCommon,
35 pub state: CompleteSMState,
36}
37
38impl CompleteStateMachine {
39 pub fn update(&self, state: CompleteSMState) -> Self {
40 Self {
41 common: self.common.clone(),
42 state,
43 }
44 }
45}
46
47impl fmt::Display for CompleteStateMachine {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(
50 f,
51 "Complete State Machine Operation ID: {:?} State: {}",
52 self.common.operation_id, self.state
53 )
54 }
55}
56
57#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
58pub struct CompleteSMCommon {
59 pub operation_id: OperationId,
60 pub payment_hash: bitcoin::hashes::sha256::Hash,
61 pub incoming_chan_id: u64,
62 pub htlc_id: u64,
63}
64
65#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
71pub struct CircuitCompleteStateMachine {
72 pub common: CircuitCompleteSMCommon,
74 pub state: CompleteSMState,
76}
77
78#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
80pub struct CircuitCompleteSMCommon {
81 pub operation_id: OperationId,
83 pub receive_operation_id: OperationId,
85 pub payment_hash: bitcoin::hashes::sha256::Hash,
87 pub circuit: IncomingCircuitKey,
89}
90
91#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Decodable, Encodable)]
93pub struct IncomingCircuitKey {
94 pub incoming_chan_id: u64,
96 pub htlc_id: u64,
98}
99
100async fn await_receive(
101 context: GatewayClientContextV2,
102 operation_id: OperationId,
103) -> FinalReceiveState {
104 context.module.await_receive(operation_id).await
105}
106
107async fn complete_circuit(
108 context: GatewayClientContextV2,
109 payment_hash: bitcoin::hashes::sha256::Hash,
110 final_receive_state: FinalReceiveState,
111 circuit: IncomingCircuitKey,
112) -> Result<(), LightningRpcError> {
113 let action = if let FinalReceiveState::Success(preimage) = final_receive_state {
114 PaymentAction::Settle(Preimage(preimage))
115 } else {
116 PaymentAction::Cancel
117 };
118 let IncomingCircuitKey {
119 incoming_chan_id,
120 htlc_id,
121 } = circuit;
122
123 context
124 .gateway
125 .complete_htlc(InterceptPaymentResponse {
126 incoming_chan_id,
127 htlc_id,
128 payment_hash,
129 action,
130 })
131 .await
132}
133
134async fn log_completion(
135 dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
136 client_ctx: GatewayClientContextV2,
137 payment_hash: bitcoin::hashes::sha256::Hash,
138) {
139 client_ctx
140 .module
141 .client_ctx
142 .log_event(
143 &mut dbtx.module_tx(),
144 CompleteLightningPaymentSucceeded {
145 payment_image: PaymentImage::Hash(payment_hash),
146 },
147 )
148 .await;
149}
150
151#[derive(Debug, Eq, PartialEq)]
152pub(super) enum CompletionOutcome {
153 Succeeded,
154 Failed(String),
155}
156
157pub(super) fn completion_outcome(result: Result<(), LightningRpcError>) -> CompletionOutcome {
158 match result {
159 Ok(()) => CompletionOutcome::Succeeded,
160 Err(error) => CompletionOutcome::Failed(error.to_string()),
161 }
162}
163
164#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
165pub enum CompleteSMState {
166 Pending,
167 Completing(FinalReceiveState),
168 Completed,
169 CompletionFailed(String),
171}
172
173impl fmt::Display for CompleteSMState {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 match self {
176 CompleteSMState::Pending => write!(f, "Pending"),
177 CompleteSMState::Completing(_) => write!(f, "Completing"),
178 CompleteSMState::Completed => write!(f, "Completed"),
179 CompleteSMState::CompletionFailed(_) => write!(f, "Completion Failed"),
180 }
181 }
182}
183
184impl State for CompleteStateMachine {
185 type ModuleContext = GatewayClientContextV2;
186
187 fn transitions(
188 &self,
189 context: &Self::ModuleContext,
190 _global_context: &DynGlobalClientContext,
191 ) -> Vec<StateTransition<Self>> {
192 let gateway_context = context.clone();
193 match &self.state {
194 CompleteSMState::Pending => vec![StateTransition::new(
195 await_receive(context.clone(), self.common.operation_id),
196 |_, result, old_state| {
197 Box::pin(async move { Self::transition_receive(result, &old_state) })
198 },
199 )],
200 CompleteSMState::Completing(finale_receive_state) => vec![StateTransition::new(
201 complete_circuit(
202 gateway_context.clone(),
203 self.common.payment_hash,
204 finale_receive_state.clone(),
205 IncomingCircuitKey {
206 incoming_chan_id: self.common.incoming_chan_id,
207 htlc_id: self.common.htlc_id,
208 },
209 ),
210 move |dbtx, result, old_state| {
211 Box::pin(Self::transition_completion(
212 old_state,
213 dbtx,
214 gateway_context.clone(),
215 result,
216 ))
217 },
218 )],
219 CompleteSMState::Completed | CompleteSMState::CompletionFailed(_) => Vec::new(),
220 }
221 }
222
223 fn operation_id(&self) -> OperationId {
224 self.common.operation_id
225 }
226}
227
228impl fmt::Display for CircuitCompleteStateMachine {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 write!(
231 f,
232 "Circuit Complete State Machine Operation ID: {:?} State: {}",
233 self.common.operation_id, self.state
234 )
235 }
236}
237
238impl CircuitCompleteStateMachine {
239 fn update(&self, state: CompleteSMState) -> Self {
240 Self {
241 common: self.common.clone(),
242 state,
243 }
244 }
245}
246
247impl State for CircuitCompleteStateMachine {
248 type ModuleContext = GatewayClientContextV2;
249
250 fn transitions(
251 &self,
252 context: &Self::ModuleContext,
253 _global_context: &DynGlobalClientContext,
254 ) -> Vec<StateTransition<Self>> {
255 let gateway_context = context.clone();
256 match &self.state {
257 CompleteSMState::Pending => vec![StateTransition::new(
258 await_receive(context.clone(), self.common.receive_operation_id),
259 |_, result, old_state: CircuitCompleteStateMachine| {
260 Box::pin(async move { old_state.update(CompleteSMState::Completing(result)) })
261 },
262 )],
263 CompleteSMState::Completing(final_receive_state) => vec![StateTransition::new(
264 complete_circuit(
265 gateway_context.clone(),
266 self.common.payment_hash,
267 final_receive_state.clone(),
268 self.common.circuit,
269 ),
270 move |dbtx, result, old_state: CircuitCompleteStateMachine| {
271 let gateway_context = gateway_context.clone();
272 Box::pin(async move {
273 match completion_outcome(result) {
274 CompletionOutcome::Succeeded => {
275 log_completion(
276 dbtx,
277 gateway_context,
278 old_state.common.payment_hash,
279 )
280 .await;
281 old_state.update(CompleteSMState::Completed)
282 }
283 CompletionOutcome::Failed(error) => {
284 old_state.update(CompleteSMState::CompletionFailed(error))
285 }
286 }
287 })
288 },
289 )],
290 CompleteSMState::Completed | CompleteSMState::CompletionFailed(_) => Vec::new(),
291 }
292 }
293
294 fn operation_id(&self) -> OperationId {
295 self.common.operation_id
296 }
297}
298
299impl CompleteStateMachine {
300 fn transition_receive(
301 final_receive_state: FinalReceiveState,
302 old_state: &CompleteStateMachine,
303 ) -> CompleteStateMachine {
304 old_state.update(CompleteSMState::Completing(final_receive_state))
305 }
306
307 async fn transition_completion(
308 old_state: CompleteStateMachine,
309 dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
310 client_ctx: GatewayClientContextV2,
311 result: Result<(), LightningRpcError>,
312 ) -> CompleteStateMachine {
313 match completion_outcome(result) {
314 CompletionOutcome::Succeeded => {
315 log_completion(dbtx, client_ctx, old_state.common.payment_hash).await;
316 old_state.update(CompleteSMState::Completed)
317 }
318 CompletionOutcome::Failed(error) => {
319 old_state.update(CompleteSMState::CompletionFailed(error))
320 }
321 }
322 }
323}