From 0fb234be560a10d9d3b2cd090c904e80565e4014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Wed, 16 Oct 2024 13:13:03 -0700 Subject: [PATCH 1/2] chore: claim oob refunds with a single state machine --- fedimint-client/src/sm/executor.rs | 14 +- fedimint-core/src/db/mod.rs | 2 + modules/fedimint-mint-client/src/input.rs | 2 +- modules/fedimint-mint-client/src/lib.rs | 92 ++++++------ modules/fedimint-mint-client/src/oob.rs | 158 +++++++++++++++++++-- modules/fedimint-mint-tests/tests/tests.rs | 80 ++++++++++- 6 files changed, 289 insertions(+), 59 deletions(-) diff --git a/fedimint-client/src/sm/executor.rs b/fedimint-client/src/sm/executor.rs index 977260d46ef..891863a057a 100644 --- a/fedimint-client/src/sm/executor.rs +++ b/fedimint-client/src/sm/executor.rs @@ -228,18 +228,20 @@ impl Executor { if let Some(module_context) = self.inner.module_contexts.get(&state.module_instance_id()) { - let context = self + if let Some(context) = self .inner .state .read() - .unwrap() + .expect("locking failed") .gen_context(&state) - .expect("executor should be running at this point"); - - if state.is_terminal(module_context, &context) { - return Err(AddStateMachinesError::Other(anyhow!( + { + if state.is_terminal(module_context, &context) { + return Err(AddStateMachinesError::Other(anyhow!( "State is already terminal, adding it to the executor doesn't make sense." ))); + } + } else { + warn!(target: LOG_CLIENT_REACTOR, "Executor should be running at this point"); } } diff --git a/fedimint-core/src/db/mod.rs b/fedimint-core/src/db/mod.rs index 0cae3160ff7..aaa38deceb4 100644 --- a/fedimint-core/src/db/mod.rs +++ b/fedimint-core/src/db/mod.rs @@ -572,6 +572,7 @@ impl Database { warn!( target: LOG_DB, curr_attempts, + ?err, "Database commit failed in an autocommit block - terminating" ); return Err(AutocommitError::CommitFailed { @@ -585,6 +586,7 @@ impl Database { warn!( target: LOG_DB, curr_attempts, + %err, delay_ms = %delay, "Database commit failed in an autocommit block - retrying" ); diff --git a/modules/fedimint-mint-client/src/input.rs b/modules/fedimint-mint-client/src/input.rs index a906086810e..4a534e7347e 100644 --- a/modules/fedimint-mint-client/src/input.rs +++ b/modules/fedimint-mint-client/src/input.rs @@ -163,7 +163,7 @@ impl MintInputStateCreated { #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintInputStateRefund { - refund_txid: TransactionId, + pub refund_txid: TransactionId, } impl MintInputStateRefund { diff --git a/modules/fedimint-mint-client/src/lib.rs b/modules/fedimint-mint-client/src/lib.rs index d104de03bed..363a191d102 100644 --- a/modules/fedimint-mint-client/src/lib.rs +++ b/modules/fedimint-mint-client/src/lib.rs @@ -78,6 +78,7 @@ use fedimint_mint_common::config::MintClientConfig; pub use fedimint_mint_common::*; use futures::{pin_mut, StreamExt}; use hex::ToHex; +use oob::MintOOBStatesCreatedMulti; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use tbs::{AggregatePublicKey, Signature}; @@ -92,7 +93,7 @@ use crate::client_db::{ use crate::input::{ MintInputCommon, MintInputStateCreated, MintInputStateMachine, MintInputStates, }; -use crate::oob::{MintOOBStateMachine, MintOOBStates, MintOOBStatesCreated}; +use crate::oob::{MintOOBStateMachine, MintOOBStates}; use crate::output::{ MintOutputCommon, MintOutputStateMachine, MintOutputStates, MintOutputStatesCreated, NoteIssuanceRequest, @@ -1305,18 +1306,13 @@ impl MintClientModule { MintClientModule::delete_spendable_note(&self.client_ctx, dbtx, amount, note).await; } - let mut state_machines = Vec::new(); - - for (amount, spendable_note) in selected_notes.clone().into_iter_items() { - state_machines.push(MintClientStateMachines::OOB(MintOOBStateMachine { - operation_id, - state: MintOOBStates::Created(MintOOBStatesCreated { - amount, - spendable_note, - timeout: fedimint_core::time::now() + try_cancel_after, - }), - })); - } + let state_machines = vec![MintClientStateMachines::OOB(MintOOBStateMachine { + operation_id, + state: MintOOBStates::CreatedMulti(MintOOBStatesCreatedMulti { + spendable_notes: selected_notes.clone().into_iter_items().collect(), + timeout: fedimint_core::time::now() + try_cancel_after, + }), + })]; Ok((operation_id, state_machines, selected_notes)) } @@ -1334,13 +1330,17 @@ impl MintClientModule { match state.state { MintOOBStates::TimeoutRefund(refund) => Some(SpendOOBRefund { user_triggered: false, - transaction_id: refund.refund_txid, + transaction_ids: vec![refund.refund_txid], }), MintOOBStates::UserRefund(refund) => Some(SpendOOBRefund { user_triggered: true, - transaction_id: refund.refund_txid, + transaction_ids: vec![refund.refund_txid], }), - MintOOBStates::Created(_) => None, + MintOOBStates::UserRefundMulti(refund) => Some(SpendOOBRefund { + user_triggered: true, + transaction_ids: vec![refund.refund_txid], + }), + MintOOBStates::Created(_) | MintOOBStates::CreatedMulti(_) => None, } }), ) @@ -1771,33 +1771,45 @@ impl MintClientModule { if refund.user_triggered { yield SpendOOBState::UserCanceledProcessing; + } - match client_ctx - .transaction_updates(operation_id) - .await - .await_tx_accepted(refund.transaction_id) - .await - { - Ok(()) => { - yield SpendOOBState::UserCanceledSuccess; - }, - Err(_) => { - yield SpendOOBState::UserCanceledFailure; - } - } - } else { - match client_ctx + let mut success = true; + + for txid in refund.transaction_ids { + debug!( + target: LOG_CLIENT_MODULE_MINT, + %txid, + operation_id=%operation_id.fmt_short(), + "Waiting for oob refund txid" + ); + if client_ctx .transaction_updates(operation_id) .await - .await_tx_accepted(refund.transaction_id) - .await - { - Ok(()) => { - yield SpendOOBState::Refunded; - }, - Err(_) => { - yield SpendOOBState::Success; + .await_tx_accepted(txid) + .await.is_err() { + success = false; } + } + + debug!( + target: LOG_CLIENT_MODULE_MINT, + operation_id=%operation_id.fmt_short(), + %success, + "Done waiting for all refund oob txids" + ); + + match (refund.user_triggered, success) { + (true, true) => { + yield SpendOOBState::UserCanceledSuccess; + }, + (true, false) => { + yield SpendOOBState::UserCanceledFailure; + }, + (false, true) => { + yield SpendOOBState::Refunded; + }, + (false, false) => { + yield SpendOOBState::Success; } } } @@ -1867,7 +1879,7 @@ pub fn spendable_notes_to_operation_id( #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SpendOOBRefund { pub user_triggered: bool, - pub transaction_id: TransactionId, + pub transaction_ids: Vec, } /// Defines a strategy for selecting e-cash notes given a specific target amount diff --git a/modules/fedimint-mint-client/src/oob.rs b/modules/fedimint-mint-client/src/oob.rs index 12e1e61eab5..2a47b25cbd4 100644 --- a/modules/fedimint-mint-client/src/oob.rs +++ b/modules/fedimint-mint-client/src/oob.rs @@ -9,11 +9,22 @@ use fedimint_core::encoding::{Decodable, Encodable}; use fedimint_core::{runtime, Amount, TransactionId}; use fedimint_mint_common::MintInput; -use crate::input::{ - MintInputCommon, MintInputStateCreated, MintInputStateMachine, MintInputStates, -}; +use crate::input::{MintInputCommon, MintInputStateMachine, MintInputStateRefund, MintInputStates}; use crate::{MintClientContext, MintClientStateMachines, SpendableNote}; +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub enum MintOOBStatesV1 { + /// The e-cash has been taken out of the wallet and we are waiting for the + /// recipient to reissue it or the user to trigger a refund. + Created(MintOOBStatesCreated), + /// The user has triggered a refund. + UserRefund(MintOOBStatesUserRefund), + /// The timeout of this out-of-band transaction was hit and we attempted to + /// refund. This refund *failing* is the expected behavior since the + /// recipient is supposed to have already reissued it. + TimeoutRefund(MintOOBStatesTimeoutRefund), +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// State machine managing e-cash that has been taken out of the wallet for /// out-of-band transmission. @@ -27,13 +38,22 @@ use crate::{MintClientContext, MintClientStateMachines, SpendableNote}; pub enum MintOOBStates { /// The e-cash has been taken out of the wallet and we are waiting for the /// recipient to reissue it or the user to trigger a refund. - Created(MintOOBStatesCreated), + CreatedMulti(MintOOBStatesCreatedMulti), /// The user has triggered a refund. - UserRefund(MintOOBStatesUserRefund), + UserRefundMulti(MintOOBStatesUserRefundMulti), /// The timeout of this out-of-band transaction was hit and we attempted to /// refund. This refund *failing* is the expected behavior since the /// recipient is supposed to have already reissued it. TimeoutRefund(MintOOBStatesTimeoutRefund), + + // States we want to drop eventually (that's why they are last) + // - + /// Obsoleted, legacy from [`MintOOBStatesV1`], like + /// [`MintOOBStates::CreatedMulti`] but for a single note only. + Created(MintOOBStatesCreated), + /// Obsoleted, legacy from [`MintOOBStatesV1`], like + /// [`MintOOBStates::UserRefundMulti`] but for single note only + UserRefund(MintOOBStatesUserRefund), } #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] @@ -49,6 +69,20 @@ pub struct MintOOBStatesCreated { pub(crate) timeout: SystemTime, } +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOOBStatesCreatedMulti { + pub(crate) spendable_notes: Vec<(Amount, SpendableNote)>, + pub(crate) timeout: SystemTime, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOOBStatesUserRefundMulti { + /// The txid we are hoping succeeds refunding all notes in one go + pub(crate) refund_txid: TransactionId, + /// The notes we are going to refund individually if it doesn't + pub(crate) spendable_notes: Vec<(Amount, SpendableNote)>, +} + #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintOOBStatesUserRefund { pub(crate) refund_txid: TransactionId, @@ -71,7 +105,12 @@ impl State for MintOOBStateMachine { MintOOBStates::Created(created) => { created.transitions(self.operation_id, context, global_context) } - MintOOBStates::UserRefund(_) | MintOOBStates::TimeoutRefund(_) => { + MintOOBStates::CreatedMulti(created) => { + created.transitions(self.operation_id, context, global_context) + } + MintOOBStates::UserRefund(_) + | MintOOBStates::TimeoutRefund(_) + | MintOOBStates::UserRefundMulti(_) => { vec![] } } @@ -112,6 +151,40 @@ impl MintOOBStatesCreated { } } +impl MintOOBStatesCreatedMulti { + fn transitions( + &self, + operation_id: OperationId, + context: &MintClientContext, + global_context: &DynGlobalClientContext, + ) -> Vec> { + let user_cancel_gc = global_context.clone(); + let timeout_cancel_gc = global_context.clone(); + vec![ + StateTransition::new( + context.await_cancel_oob_payment(operation_id), + move |dbtx, (), state| { + Box::pin(transition_user_cancel_multi( + state, + dbtx, + user_cancel_gc.clone(), + )) + }, + ), + StateTransition::new( + await_timeout_cancel(self.timeout), + move |dbtx, (), state| { + Box::pin(transition_timeout_cancel_multi( + state, + dbtx, + timeout_cancel_gc.clone(), + )) + }, + ), + ] + } +} + async fn transition_user_cancel( prev_state: MintOOBStateMachine, dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, @@ -136,6 +209,32 @@ async fn transition_user_cancel( } } +async fn transition_user_cancel_multi( + prev_state: MintOOBStateMachine, + dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, + global_context: DynGlobalClientContext, +) -> MintOOBStateMachine { + let spendable_notes = match prev_state.state { + MintOOBStates::CreatedMulti(created) => created.spendable_notes, + _ => panic!("Invalid previous state: {prev_state:?}"), + }; + + let refund_txid = try_cancel_oob_spend_multi( + dbtx, + prev_state.operation_id, + spendable_notes.clone(), + global_context, + ) + .await; + MintOOBStateMachine { + operation_id: prev_state.operation_id, + state: MintOOBStates::UserRefundMulti(MintOOBStatesUserRefundMulti { + refund_txid, + spendable_notes, + }), + } +} + async fn await_timeout_cancel(deadline: SystemTime) { if let Ok(time_until_deadline) = deadline.duration_since(fedimint_core::time::now()) { runtime::sleep(time_until_deadline).await; @@ -166,6 +265,29 @@ async fn transition_timeout_cancel( } } +async fn transition_timeout_cancel_multi( + prev_state: MintOOBStateMachine, + dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, + global_context: DynGlobalClientContext, +) -> MintOOBStateMachine { + let spendable_notes = match prev_state.state { + MintOOBStates::CreatedMulti(created) => created.spendable_notes, + _ => panic!("Invalid previous state: {prev_state:?}"), + }; + + let refund_txid = try_cancel_oob_spend_multi( + dbtx, + prev_state.operation_id, + spendable_notes, + global_context, + ) + .await; + MintOOBStateMachine { + operation_id: prev_state.operation_id, + state: MintOOBStates::TimeoutRefund(MintOOBStatesTimeoutRefund { refund_txid }), + } +} + async fn try_cancel_oob_spend( dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, operation_id: OperationId, @@ -173,9 +295,24 @@ async fn try_cancel_oob_spend( spendable_note: SpendableNote, global_context: DynGlobalClientContext, ) -> TransactionId { - let (inputs, input_sms) = vec![spendable_note] + try_cancel_oob_spend_multi( + dbtx, + operation_id, + vec![(amount, spendable_note)], + global_context, + ) + .await +} + +async fn try_cancel_oob_spend_multi( + dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, + operation_id: OperationId, + spendable_notes: Vec<(Amount, SpendableNote)>, + global_context: DynGlobalClientContext, +) -> TransactionId { + let (inputs, input_sms) = spendable_notes .into_iter() - .map(|spendable_note| { + .map(|(amount, spendable_note)| { ( ClientInput { input: MintInput::new_v0(amount, spendable_note.note()), @@ -190,9 +327,8 @@ async fn try_cancel_oob_spend( txid, input_idx, }, - state: MintInputStates::Created(MintInputStateCreated { - amount, - spendable_note, + state: MintInputStates::Refund(MintInputStateRefund { + refund_txid: txid, }), })] }), diff --git a/modules/fedimint-mint-tests/tests/tests.rs b/modules/fedimint-mint-tests/tests/tests.rs index a46793fb016..99491648bb0 100644 --- a/modules/fedimint-mint-tests/tests/tests.rs +++ b/modules/fedimint-mint-tests/tests/tests.rs @@ -11,7 +11,7 @@ use fedimint_core::fee_consensus::FeeConsensus; use fedimint_core::secp256k1::KeyPair; use fedimint_core::task::sleep_in_test; use fedimint_core::util::NextOrPending; -use fedimint_core::{sats, secp256k1, Amount}; +use fedimint_core::{sats, secp256k1, Amount, TieredMulti}; use fedimint_dummy_client::{DummyClientInit, DummyClientModule}; use fedimint_dummy_common::config::DummyGenParams; use fedimint_dummy_server::DummyInit; @@ -318,6 +318,84 @@ async fn sends_ecash_out_of_band_cancel() -> anyhow::Result<()> { panic!("Did not receive refund in time"); } +#[tokio::test(flavor = "multi_thread")] +#[ignore = "We want this to work eventually, but we need to rewrite the sm created by claim_input to impl the note-by-note refund"] +async fn sends_ecash_out_of_band_cancel_partial() -> anyhow::Result<()> { + let fed = fixtures().new_default_fed().await; + let (client, client2) = fed.two_clients().await; + let dummy_module = client.get_first_module::()?; + info!("### PRINT NOTES"); + let (print_op, outpoint) = dummy_module.print_money(sats(1000)).await?; + client + .await_primary_module_output(print_op, outpoint) + .await?; + + let client2_mint = client2.get_first_module::()?; + + // Spend from client1 to client2 + info!("### SPEND NOTES"); + let mint_module = client.get_first_module::()?; + let (spend_op, notes) = mint_module + .spend_notes_with_selector( + &SelectNotesWithAtleastAmount, + sats(750), + TIMEOUT * 3, + false, + (), + ) + .await?; + let sub1 = &mut mint_module + .subscribe_spend_notes(spend_op) + .await? + .into_stream(); + assert_eq!(sub1.ok().await?, SpendOOBState::Created); + + let oob_notes = notes.notes().clone(); + let federation_id = notes.federation_id_prefix(); + let mut oob_notes_iter = oob_notes.into_iter_items().rev(); + let single_note = oob_notes_iter.next().unwrap(); + let oob_notes_single_note = TieredMulti::from_iter(vec![single_note]); + + let oob_notes_single_note = OOBNotes::new(federation_id, oob_notes_single_note); + + info!("### REISSUE NOTES (single note)"); + let reissue_op = client2_mint + .reissue_external_notes(oob_notes_single_note, ()) + .await?; + + let sub2 = client2_mint + .subscribe_reissue_external_notes(reissue_op) + .await?; + + let mut sub2 = sub2.into_stream(); + info!("### SUB2: WAIT CREATED"); + assert_eq!(sub2.ok().await?, ReissueExternalNotesState::Created); + info!("### SUB2: WAIT ISSUING"); + assert_eq!(sub2.ok().await?, ReissueExternalNotesState::Issuing); + info!("### SUB2: WAIT DONE"); + assert_eq!(sub2.ok().await?, ReissueExternalNotesState::Done); + info!("### REISSUE: DONE"); + + info!("### CANCEL NOTES"); + mint_module.try_cancel_spend_notes(spend_op).await; + assert_eq!(sub1.ok().await?, SpendOOBState::UserCanceledProcessing); + info!("### CANCEL NOTES: must fail"); + assert_eq!(sub1.ok().await?, SpendOOBState::UserCanceledFailure); + + // FIXME: UserCanceledSuccess should mean the money is in our wallet + for _ in 0..120 { + let balance = client.get_balance().await; + let expected_min_balance = sats(1000) - EXPECTED_MAXIMUM_FEE - single_note.0; + info!(target: LOG_TEST, %balance, %expected_min_balance, "Checking balance"); + if expected_min_balance <= balance { + return Ok(()); + } + sleep_in_test("waiting for wallet balance", Duration::from_millis(500)).await; + } + + panic!("Did not receive refund in time"); +} + #[tokio::test(flavor = "multi_thread")] async fn error_zero_value_oob_spend() -> anyhow::Result<()> { // Print notes for client1 From d2a88ce28c3322c7b8330fdcbb431273c8da2d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Mon, 28 Oct 2024 16:58:11 -0700 Subject: [PATCH 2/2] chore: migrate old mint oob state --- modules/fedimint-dummy-client/src/db.rs | 2 ++ modules/fedimint-mint-client/src/client_db.rs | 35 ++++++++++++++++++- modules/fedimint-mint-client/src/lib.rs | 9 +++-- modules/fedimint-mint-client/src/oob.rs | 6 ++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/modules/fedimint-dummy-client/src/db.rs b/modules/fedimint-dummy-client/src/db.rs index 731e42747c5..c0a2200f0d8 100644 --- a/modules/fedimint-dummy-client/src/db.rs +++ b/modules/fedimint-dummy-client/src/db.rs @@ -79,6 +79,8 @@ pub(crate) fn get_v1_migrated_state( let decoders = ModuleDecoderRegistry::default(); let dummy_sm_variant = u16::consensus_decode(cursor, &decoders)?; + // We are only migrating the type of one of the variants, so we do nothing on + // other discriminants. if dummy_sm_variant != 5 { return Ok(None); } diff --git a/modules/fedimint-mint-client/src/client_db.rs b/modules/fedimint-mint-client/src/client_db.rs index 67fda26cf2a..af43e56edda 100644 --- a/modules/fedimint-mint-client/src/client_db.rs +++ b/modules/fedimint-mint-client/src/client_db.rs @@ -1,14 +1,18 @@ +use std::io::Cursor; + use fedimint_client::module::init::recovery::RecoveryFromHistoryCommon; use fedimint_core::core::OperationId; use fedimint_core::db::{DatabaseTransaction, IDatabaseTransactionOpsCoreTyped as _}; use fedimint_core::encoding::{Decodable, Encodable}; +use fedimint_core::module::registry::ModuleDecoderRegistry; use fedimint_core::{impl_db_lookup, impl_db_record, Amount}; use fedimint_mint_common::Nonce; use serde::Serialize; use strum_macros::EnumIter; use crate::backup::recovery::MintRecoveryState; -use crate::SpendableNoteUndecoded; +use crate::oob::{MintOOBStateMachine, MintOOBStateMachineV1, MintOOBStates, MintOOBStatesV1}; +use crate::{MintClientStateMachines, SpendableNoteUndecoded}; #[repr(u8)] #[derive(Clone, EnumIter, Debug)] @@ -110,3 +114,32 @@ pub async fn migrate_to_v1( Ok(None) } + +/// Maps all `Unreachable` states in the state machine to `OutputDone` +pub(crate) fn migrate_state_to_v2( + operation_id: OperationId, + cursor: &mut Cursor<&[u8]>, +) -> anyhow::Result, OperationId)>> { + let decoders = ModuleDecoderRegistry::default(); + + let mint_client_state_machine_variant = u16::consensus_decode(cursor, &decoders)?; + + let bytes = match mint_client_state_machine_variant { + 2 => { + let old_state = MintOOBStateMachineV1::consensus_decode(cursor, &decoders)?; + + let new_state = match old_state.state { + MintOOBStatesV1::Created(created) => MintOOBStates::Created(created), + MintOOBStatesV1::UserRefund(refund) => MintOOBStates::UserRefund(refund), + MintOOBStatesV1::TimeoutRefund(refund) => MintOOBStates::TimeoutRefund(refund), + }; + MintClientStateMachines::OOB(MintOOBStateMachine { + operation_id: old_state.operation_id, + state: new_state, + }) + .consensus_encode_to_vec() + } + _ => return Ok(None), + }; + Ok(Some((bytes, operation_id))) +} diff --git a/modules/fedimint-mint-client/src/lib.rs b/modules/fedimint-mint-client/src/lib.rs index 363a191d102..44738776d02 100644 --- a/modules/fedimint-mint-client/src/lib.rs +++ b/modules/fedimint-mint-client/src/lib.rs @@ -35,9 +35,11 @@ use async_stream::{stream, try_stream}; use backup::recovery::MintRecovery; use base64::Engine as _; use bitcoin_hashes::{sha256, sha256t, Hash, HashEngine as BitcoinHashEngine}; -use client_db::{migrate_to_v1, DbKeyPrefix, NoteKeyPrefix, RecoveryFinalizedKey}; +use client_db::{ + migrate_state_to_v2, migrate_to_v1, DbKeyPrefix, NoteKeyPrefix, RecoveryFinalizedKey, +}; use event::NoteSpent; -use fedimint_client::db::ClientMigrationFn; +use fedimint_client::db::{migrate_state, ClientMigrationFn}; use fedimint_client::module::init::{ ClientModuleInit, ClientModuleInitArgs, ClientModuleRecoverArgs, }; @@ -611,6 +613,9 @@ impl ClientModuleInit for MintClientInit { migrations.insert(DatabaseVersion(0), |dbtx, _, _| { Box::pin(migrate_to_v1(dbtx)) }); + migrations.insert(DatabaseVersion(1), |_, active_states, inactive_states| { + Box::pin(async { migrate_state(active_states, inactive_states, migrate_state_to_v2) }) + }); migrations } diff --git a/modules/fedimint-mint-client/src/oob.rs b/modules/fedimint-mint-client/src/oob.rs index 2a47b25cbd4..e0f84dbca0a 100644 --- a/modules/fedimint-mint-client/src/oob.rs +++ b/modules/fedimint-mint-client/src/oob.rs @@ -56,6 +56,12 @@ pub enum MintOOBStates { UserRefund(MintOOBStatesUserRefund), } +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOOBStateMachineV1 { + pub(crate) operation_id: OperationId, + pub(crate) state: MintOOBStatesV1, +} + #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintOOBStateMachine { pub(crate) operation_id: OperationId,