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