states.rs
1 use fedimint_client::sm::{DynState, State, StateTransition}; 2 use fedimint_client::DynGlobalClientContext; 3 use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, OperationId}; 4 use fedimint_core::db::{DatabaseTransaction, IDatabaseTransactionOpsCoreTyped}; 5 use fedimint_core::encoding::{Decodable, Encodable}; 6 use fedimint_core::{Amount, TransactionId}; 7 use serde::{Deserialize, Serialize}; 8 use thiserror::Error; 9 10 use crate::db::DummyClientFundsKeyV1; 11 use crate::{get_funds, DummyClientContext}; 12 13 /// Tracks a transaction 14 #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] 15 pub 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 24 impl 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 76 async 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 82 async 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 90 impl 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)] 99 pub enum DummyError { 100 #[error("Dummy module had an internal error")] 101 DummyInternalError, 102 }