fedimint_gwv2_client/
complete_sm.rs

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/// State machine that completes the incoming payment by contacting the
16/// lightning node when the incoming contract has been funded and the preimage
17/// is available.
18///
19/// ```mermaid
20/// graph LR
21/// classDef virtual fill:#fff,stroke-dasharray: 5 5
22///
23///    Pending -- receive preimage or fail --> Completing
24///    Completing -- htlc is completed  --> Completed
25/// ```
26
27#[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}