fedimint_dummy_client/
states.rs

1use fedimint_client_module::DynGlobalClientContext;
2use fedimint_client_module::sm::{DynState, State, StateTransition};
3use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, OperationId};
4use fedimint_core::db::{DatabaseTransaction, IDatabaseTransactionOpsCoreTyped};
5use fedimint_core::encoding::{Decodable, Encodable};
6use fedimint_core::module::AmountUnit;
7use fedimint_core::{Amount, TransactionId};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::db::DummyClientFundsKey;
12use crate::{DummyClientContext, get_funds};
13
14/// Tracks a transaction
15#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
16pub enum DummyStateMachineV1 {
17    Input(Amount, TransactionId, OperationId),
18    Output(Amount, TransactionId, OperationId),
19    InputDone(OperationId),
20    OutputDone(Amount, TransactionId, OperationId),
21    Refund(OperationId),
22    Unreachable(OperationId, Amount),
23}
24
25/// Tracks a transaction
26#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
27pub enum DummyStateMachine {
28    Input(Amount, AmountUnit, TransactionId, OperationId),
29    Output(Amount, AmountUnit, TransactionId, OperationId),
30    InputDone(OperationId),
31    OutputDone(Amount, AmountUnit, TransactionId, OperationId),
32    Refund(OperationId),
33    Unreachable(OperationId, Amount),
34}
35
36impl State for DummyStateMachine {
37    type ModuleContext = DummyClientContext;
38
39    fn transitions(
40        &self,
41        _context: &Self::ModuleContext,
42        global_context: &DynGlobalClientContext,
43    ) -> Vec<StateTransition<Self>> {
44        match self.clone() {
45            DummyStateMachine::Input(amount, unit, txid, id) => vec![StateTransition::new(
46                await_tx_accepted(global_context.clone(), txid),
47                move |dbtx, res, _state: Self| match res {
48                    // accepted, we are done
49                    Ok(()) => Box::pin(async move { DummyStateMachine::InputDone(id) }),
50                    // tx rejected, we refund ourselves
51                    Err(_) => Box::pin(async move {
52                        add_funds(amount, unit, dbtx.module_tx()).await;
53                        DummyStateMachine::Refund(id)
54                    }),
55                },
56            )],
57            DummyStateMachine::Output(amount, unit, txid, id) => vec![StateTransition::new(
58                await_tx_accepted(global_context.clone(), txid),
59                move |dbtx, res, _state: Self| match res {
60                    // output accepted, add funds
61                    Ok(()) => Box::pin(async move {
62                        add_funds(amount, unit, dbtx.module_tx()).await;
63                        DummyStateMachine::OutputDone(amount, unit, txid, id)
64                    }),
65                    // output rejected, do not add funds
66                    Err(_) => Box::pin(async move { DummyStateMachine::Refund(id) }),
67                },
68            )],
69            DummyStateMachine::InputDone(_)
70            | DummyStateMachine::OutputDone(_, _, _, _)
71            | DummyStateMachine::Refund(_)
72            | DummyStateMachine::Unreachable(_, _) => vec![],
73        }
74    }
75
76    fn operation_id(&self) -> OperationId {
77        match self {
78            DummyStateMachine::Input(_, _, _, id)
79            | DummyStateMachine::Output(_, _, _, id)
80            | DummyStateMachine::InputDone(id)
81            | DummyStateMachine::OutputDone(_, _, _, id)
82            | DummyStateMachine::Refund(id)
83            | DummyStateMachine::Unreachable(id, _) => *id,
84        }
85    }
86}
87
88async fn add_funds(amount: Amount, unit: AmountUnit, mut dbtx: DatabaseTransaction<'_>) {
89    let funds = get_funds(&mut dbtx, unit).await + amount;
90    dbtx.insert_entry(&DummyClientFundsKey { unit }, &funds)
91        .await;
92}
93
94// TODO: Boiler-plate, should return OutputOutcome
95async fn await_tx_accepted(
96    context: DynGlobalClientContext,
97    txid: TransactionId,
98) -> Result<(), String> {
99    context.await_tx_accepted(txid).await
100}
101
102// TODO: Boiler-plate
103impl IntoDynInstance for DummyStateMachine {
104    type DynType = DynState;
105
106    fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
107        DynState::from_typed(instance_id, self)
108    }
109}
110
111#[derive(Error, Debug, Serialize, Deserialize, Encodable, Decodable, Clone, Eq, PartialEq)]
112pub enum DummyError {
113    #[error("Dummy module had an internal error")]
114    DummyInternalError,
115}