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, 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)]
28pub struct CompleteStateMachine {
29 pub common: CompleteSMCommon,
30 pub state: CompleteSMState,
31}
32
33impl CompleteStateMachine {
34 pub fn update(&self, state: CompleteSMState) -> Self {
35 Self {
36 common: self.common.clone(),
37 state,
38 }
39 }
40}
41
42impl fmt::Display for CompleteStateMachine {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(
45 f,
46 "Complete State Machine Operation ID: {:?} State: {}",
47 self.common.operation_id, self.state
48 )
49 }
50}
51
52#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
53pub struct CompleteSMCommon {
54 pub operation_id: OperationId,
55 pub payment_hash: bitcoin::hashes::sha256::Hash,
56 pub incoming_chan_id: u64,
57 pub htlc_id: u64,
58}
59
60#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
61pub enum CompleteSMState {
62 Pending,
63 Completing(FinalReceiveState),
64 Completed,
65}
66
67impl fmt::Display for CompleteSMState {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 CompleteSMState::Pending => write!(f, "Pending"),
71 CompleteSMState::Completing(_) => write!(f, "Completing"),
72 CompleteSMState::Completed => write!(f, "Completed"),
73 }
74 }
75}
76
77impl State for CompleteStateMachine {
78 type ModuleContext = GatewayClientContextV2;
79
80 fn transitions(
81 &self,
82 context: &Self::ModuleContext,
83 _global_context: &DynGlobalClientContext,
84 ) -> Vec<StateTransition<Self>> {
85 let gateway_context = context.clone();
86 match &self.state {
87 CompleteSMState::Pending => vec![StateTransition::new(
88 Self::await_receive(context.clone(), self.common.operation_id),
89 |_, result, old_state| {
90 Box::pin(async move { Self::transition_receive(result, &old_state) })
91 },
92 )],
93 CompleteSMState::Completing(finale_receive_state) => vec![StateTransition::new(
94 Self::await_completion(
95 gateway_context.clone(),
96 self.common.payment_hash,
97 finale_receive_state.clone(),
98 self.common.incoming_chan_id,
99 self.common.htlc_id,
100 ),
101 move |dbtx, (), old_state| {
102 Box::pin(Self::transition_completion(
103 old_state,
104 dbtx,
105 gateway_context.clone(),
106 ))
107 },
108 )],
109 CompleteSMState::Completed => Vec::new(),
110 }
111 }
112
113 fn operation_id(&self) -> OperationId {
114 self.common.operation_id
115 }
116}
117
118impl CompleteStateMachine {
119 async fn await_receive(
120 context: GatewayClientContextV2,
121 operation_id: OperationId,
122 ) -> FinalReceiveState {
123 context.module.await_receive(operation_id).await
124 }
125
126 fn transition_receive(
127 final_receive_state: FinalReceiveState,
128 old_state: &CompleteStateMachine,
129 ) -> CompleteStateMachine {
130 old_state.update(CompleteSMState::Completing(final_receive_state))
131 }
132
133 async fn await_completion(
134 context: GatewayClientContextV2,
135 payment_hash: bitcoin::hashes::sha256::Hash,
136 final_receive_state: FinalReceiveState,
137 incoming_chan_id: u64,
138 htlc_id: u64,
139 ) {
140 let action = if let FinalReceiveState::Success(preimage) = final_receive_state {
141 PaymentAction::Settle(Preimage(preimage))
142 } else {
143 PaymentAction::Cancel
144 };
145
146 let intercept_htlc_response = InterceptPaymentResponse {
147 incoming_chan_id,
148 htlc_id,
149 payment_hash,
150 action,
151 };
152
153 context.gateway.complete_htlc(intercept_htlc_response).await;
154 }
155
156 async fn transition_completion(
157 old_state: CompleteStateMachine,
158 dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
159 client_ctx: GatewayClientContextV2,
160 ) -> CompleteStateMachine {
161 client_ctx
162 .module
163 .client_ctx
164 .log_event(
165 &mut dbtx.module_tx(),
166 CompleteLightningPaymentSucceeded {
167 payment_image: PaymentImage::Hash(old_state.common.payment_hash),
168 },
169 )
170 .await;
171 old_state.update(CompleteSMState::Completed)
172 }
173}