1use std::time::Duration;
2
3use fedimint_client::sm::{DynState, State, StateTransition};
4use fedimint_client::DynGlobalClientContext;
5use fedimint_core::core::{Decoder, IntoDynInstance, ModuleInstanceId, OperationId};
6use fedimint_core::db::{DatabaseTransaction, IDatabaseTransactionOpsCoreTyped};
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::task::sleep;
9use fedimint_core::{Amount, OutPoint, TransactionId};
10use fedimint_dummy_common::DummyOutputOutcome;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13use tracing::debug;
14
15use crate::db::DummyClientFundsKeyV1;
16use crate::{get_funds, DummyClientContext};
17
18const RETRY_DELAY: Duration = Duration::from_secs(1);
19
20#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
22pub enum DummyStateMachine {
23 Input(Amount, TransactionId, OperationId),
24 Output(Amount, TransactionId, OperationId),
25 InputDone(OperationId),
26 OutputDone(Amount, TransactionId, OperationId),
27 Refund(OperationId),
28 Unreachable(OperationId, Amount),
29}
30
31impl State for DummyStateMachine {
32 type ModuleContext = DummyClientContext;
33
34 fn transitions(
35 &self,
36 context: &Self::ModuleContext,
37 global_context: &DynGlobalClientContext,
38 ) -> Vec<StateTransition<Self>> {
39 match self.clone() {
40 DummyStateMachine::Input(amount, txid, id) => vec![StateTransition::new(
41 await_tx_accepted(global_context.clone(), txid),
42 move |dbtx, res, _state: Self| match res {
43 Ok(()) => Box::pin(async move { DummyStateMachine::InputDone(id) }),
45 Err(_) => Box::pin(async move {
47 add_funds(amount, dbtx.module_tx()).await;
48 DummyStateMachine::Refund(id)
49 }),
50 },
51 )],
52 DummyStateMachine::Output(amount, txid, id) => vec![StateTransition::new(
53 await_dummy_output_outcome(
54 global_context.clone(),
55 OutPoint { txid, out_idx: 0 },
56 context.dummy_decoder.clone(),
57 ),
58 move |dbtx, res, _state: Self| match res {
59 Ok(()) => Box::pin(async move {
61 add_funds(amount, dbtx.module_tx()).await;
62 DummyStateMachine::OutputDone(amount, txid, id)
63 }),
64 Err(_) => Box::pin(async move { DummyStateMachine::Refund(id) }),
66 },
67 )],
68 DummyStateMachine::InputDone(_)
69 | DummyStateMachine::OutputDone(_, _, _)
70 | DummyStateMachine::Refund(_)
71 | DummyStateMachine::Unreachable(_, _) => vec![],
72 }
73 }
74
75 fn operation_id(&self) -> OperationId {
76 match self {
77 DummyStateMachine::Input(_, _, id)
78 | DummyStateMachine::Output(_, _, id)
79 | DummyStateMachine::InputDone(id)
80 | DummyStateMachine::OutputDone(_, _, id)
81 | DummyStateMachine::Refund(id)
82 | DummyStateMachine::Unreachable(id, _) => *id,
83 }
84 }
85}
86
87async fn add_funds(amount: Amount, mut dbtx: DatabaseTransaction<'_>) {
88 let funds = get_funds(&mut dbtx).await + amount;
89 dbtx.insert_entry(&DummyClientFundsKeyV1, &funds).await;
90}
91
92async fn await_tx_accepted(
94 context: DynGlobalClientContext,
95 txid: TransactionId,
96) -> Result<(), String> {
97 context.await_tx_accepted(txid).await
98}
99
100async fn await_dummy_output_outcome(
101 global_context: DynGlobalClientContext,
102 outpoint: OutPoint,
103 module_decoder: Decoder,
104) -> Result<(), DummyError> {
105 loop {
106 match global_context
107 .api()
108 .await_output_outcome::<DummyOutputOutcome>(
109 outpoint,
110 Duration::from_millis(i32::MAX as u64),
111 &module_decoder,
112 )
113 .await
114 {
115 Ok(_) => {
116 return Ok(());
117 }
118 Err(e) if e.is_rejected() => {
119 return Err(DummyError::DummyInternalError);
120 }
121 Err(e) => {
122 e.report_if_important();
123 debug!(error = %e, "Awaiting output outcome failed, retrying");
124 }
125 }
126 sleep(RETRY_DELAY).await;
127 }
128}
129
130impl IntoDynInstance for DummyStateMachine {
132 type DynType = DynState;
133
134 fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
135 DynState::from_typed(instance_id, self)
136 }
137}
138
139#[derive(Error, Debug, Serialize, Deserialize, Encodable, Decodable, Clone, Eq, PartialEq)]
140pub enum DummyError {
141 #[error("Dummy module had an internal error")]
142 DummyInternalError,
143}