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