Skip to main content

fedimint_dummy_client/
input_sm.rs

1use fedimint_client_module::DynGlobalClientContext;
2use fedimint_client_module::sm::{ClientSMDatabaseTransaction, State, StateTransition};
3use fedimint_core::core::OperationId;
4use fedimint_core::db::IDatabaseTransactionOpsCoreTyped;
5use fedimint_core::encoding::{Decodable, Encodable};
6use fedimint_core::module::AmountUnit;
7use fedimint_core::{Amount, OutPoint};
8
9use crate::DummyClientContext;
10use crate::db::DummyClientFundsKey;
11
12/// State machine tracking spending (inputs).
13///
14/// Balance is subtracted immediately when created. On rejection, balance is
15/// refunded.
16#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
17pub struct DummyInputStateMachine {
18    pub common: DummyInputSMCommon,
19    pub state: DummyInputSMState,
20}
21
22#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
23pub struct DummyInputSMCommon {
24    pub operation_id: OperationId,
25    pub out_point: OutPoint,
26    pub amount: Amount,
27    pub unit: AmountUnit,
28}
29
30#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
31pub enum DummyInputSMState {
32    Created,
33    Accepted,
34    Refunded,
35}
36
37impl DummyInputStateMachine {
38    fn update(&self, state: DummyInputSMState) -> Self {
39        Self {
40            common: self.common.clone(),
41            state,
42        }
43    }
44}
45
46impl State for DummyInputStateMachine {
47    type ModuleContext = DummyClientContext;
48
49    fn transitions(
50        &self,
51        context: &Self::ModuleContext,
52        global_context: &DynGlobalClientContext,
53    ) -> Vec<StateTransition<Self>> {
54        match self.state {
55            DummyInputSMState::Created => {
56                let global = global_context.clone();
57                let txid = self.common.out_point.txid;
58                let balance_update_sender = context.balance_update_sender.clone();
59
60                vec![StateTransition::new(
61                    async move { global.await_tx_accepted(txid).await },
62                    move |dbtx, result, old_state| {
63                        Box::pin(Self::transition_created(
64                            dbtx,
65                            result,
66                            old_state,
67                            balance_update_sender.clone(),
68                        ))
69                    },
70                )]
71            }
72            DummyInputSMState::Accepted | DummyInputSMState::Refunded => vec![],
73        }
74    }
75
76    fn operation_id(&self) -> OperationId {
77        self.common.operation_id
78    }
79}
80
81impl DummyInputStateMachine {
82    async fn transition_created(
83        dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
84        result: Result<(), String>,
85        old_state: Self,
86        balance_update_sender: tokio::sync::watch::Sender<()>,
87    ) -> Self {
88        if result.is_ok() {
89            old_state.update(DummyInputSMState::Accepted)
90        } else {
91            let current = dbtx
92                .module_tx()
93                .get_value(&DummyClientFundsKey(old_state.common.unit))
94                .await
95                .unwrap_or(Amount::ZERO);
96
97            dbtx.module_tx()
98                .insert_entry(
99                    &DummyClientFundsKey(old_state.common.unit),
100                    &(current + old_state.common.amount),
101                )
102                .await;
103
104            dbtx.module_tx().on_commit(move || {
105                balance_update_sender.send_replace(());
106            });
107
108            old_state.update(DummyInputSMState::Refunded)
109        }
110    }
111}