From 7da477ae7f6ead765bac2a065b2d01f008731731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Mon, 4 Nov 2024 16:30:36 -0800 Subject: [PATCH 1/3] refactor(client): handle multiple outputs with a single state machine --- modules/fedimint-mint-client/src/backup.rs | 33 ++- .../src/backup/recovery.rs | 8 +- modules/fedimint-mint-client/src/lib.rs | 42 ++-- modules/fedimint-mint-client/src/output.rs | 216 ++++++++++++++++-- 4 files changed, 257 insertions(+), 42 deletions(-) diff --git a/modules/fedimint-mint-client/src/backup.rs b/modules/fedimint-mint-client/src/backup.rs index 976a46b38d7..69fc13f4edb 100644 --- a/modules/fedimint-mint-client/src/backup.rs +++ b/modules/fedimint-mint-client/src/backup.rs @@ -93,13 +93,40 @@ impl MintClientModule { MintClientStateMachines::Output(MintOutputStateMachine { common, state: crate::output::MintOutputStates::Created(created_state), - }) => Some(( - common.out_point, + }) => Some(vec![( + OutPoint { + txid: common.txid, + // MintOutputStates::Created always has one out_idx + out_idx: *common.out_idxs.start(), + }, created_state.amount, created_state.issuance_request, - )), + )]), + MintClientStateMachines::Output(MintOutputStateMachine { + common, + state: crate::output::MintOutputStates::CreatedMulti(created_state), + }) => Some( + common + .out_idxs + .map(|out_idx| { + let issuance_request = created_state + .issuance_requests + .get(&out_idx) + .expect("Must have corresponding out_idx"); + ( + OutPoint { + txid: common.txid, + out_idx, + }, + issuance_request.0, + issuance_request.1, + ) + }) + .collect(), + ), _ => None, }) + .flatten() .collect::>(); let mut idxes = vec![]; diff --git a/modules/fedimint-mint-client/src/backup/recovery.rs b/modules/fedimint-mint-client/src/backup/recovery.rs index 6a44ce3bae1..62fa432f261 100644 --- a/modules/fedimint-mint-client/src/backup/recovery.rs +++ b/modules/fedimint-mint-client/src/backup/recovery.rs @@ -1,6 +1,6 @@ use std::cmp::max; use std::collections::BTreeMap; -use std::fmt; +use std::{fmt, ops}; use fedimint_client::module::init::recovery::{RecoveryFromHistory, RecoveryFromHistoryCommon}; use fedimint_client::module::init::ClientModuleRecoverArgs; @@ -217,7 +217,11 @@ impl RecoveryFromHistory for MintRecovery { MintOutputStateMachine { common: MintOutputCommon { operation_id: OperationId::new_random(), - out_point, + txid: out_point.txid, + out_idxs: ops::RangeInclusive::new( + out_point.out_idx, + out_point.out_idx, + ), }, state: crate::output::MintOutputStates::Created( MintOutputStatesCreated { diff --git a/modules/fedimint-mint-client/src/lib.rs b/modules/fedimint-mint-client/src/lib.rs index 6fa5cb0ebc0..edb0ace85bc 100644 --- a/modules/fedimint-mint-client/src/lib.rs +++ b/modules/fedimint-mint-client/src/lib.rs @@ -81,6 +81,7 @@ use futures::{pin_mut, StreamExt}; use hex::ToHex; use input::MintInputStateCreatedBundle; use oob::MintOOBStatesCreatedMulti; +use output::MintOutputStatesCreatedMulti; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use tbs::{AggregatePublicKey, Signature}; @@ -95,8 +96,7 @@ use crate::client_db::{ use crate::input::{MintInputCommon, MintInputStateMachine, MintInputStates}; use crate::oob::{MintOOBStateMachine, MintOOBStates}; use crate::output::{ - MintOutputCommon, MintOutputStateMachine, MintOutputStates, MintOutputStatesCreated, - NoteIssuanceRequest, + MintOutputCommon, MintOutputStateMachine, MintOutputStates, NoteIssuanceRequest, }; const MINT_E_CASH_TYPE_CHILD_ID: ChildId = ChildId(0); @@ -1065,7 +1065,7 @@ impl MintClientModule { ); let mut outputs = Vec::new(); - let mut output_states = Vec::new(); + let mut issuance_requests = Vec::new(); for (amount, num) in denominations.iter() { for _ in 0..num { @@ -1081,28 +1081,22 @@ impl MintClientModule { amount, }); - output_states.push(MintOutputStatesCreated { - amount, - issuance_request, - }); + issuance_requests.push((amount, issuance_request)); } } let state_generator = Arc::new(move |txid, out_idxs: RangeInclusive| { - out_idxs - .clone() - .flat_map(|out_idx| { - let output_i = (out_idx - out_idxs.clone().start()) as usize; - let output_state = output_states.get(output_i).copied().unwrap(); - vec![MintClientStateMachines::Output(MintOutputStateMachine { - common: MintOutputCommon { - operation_id, - out_point: OutPoint { txid, out_idx }, - }, - state: MintOutputStates::Created(output_state), - })] - }) - .collect() + assert_eq!(out_idxs.clone().count(), issuance_requests.len()); + vec![MintClientStateMachines::Output(MintOutputStateMachine { + common: MintOutputCommon { + operation_id, + txid, + out_idxs: out_idxs.clone(), + }, + state: MintOutputStates::CreatedMulti(MintOutputStatesCreatedMulti { + issuance_requests: out_idxs.zip(issuance_requests.clone()).collect(), + }), + })] }); assert!(!outputs.is_empty()); @@ -1146,7 +1140,9 @@ impl MintClientModule { return None; }; - if state.common.out_point != out_point { + if state.common.txid != out_point.txid + || !state.common.out_idxs.contains(&out_point.out_idx) + { return None; } @@ -1157,7 +1153,7 @@ impl MintClientModule { "Failed to finalize transaction: {}", failed.error ))), - MintOutputStates::Created(_) => None, + MintOutputStates::Created(_) | MintOutputStates::CreatedMulti(_) => None, } }); pin_mut!(stream); diff --git a/modules/fedimint-mint-client/src/output.rs b/modules/fedimint-mint-client/src/output.rs index 53b6b10bfaf..9c0eb0d46e3 100644 --- a/modules/fedimint-mint-client/src/output.rs +++ b/modules/fedimint-mint-client/src/output.rs @@ -15,7 +15,7 @@ use fedimint_core::encoding::{Decodable, Encodable}; use fedimint_core::module::ApiRequestErased; use fedimint_core::secp256k1_29::{Keypair, Secp256k1, Signing}; use fedimint_core::task::sleep; -use fedimint_core::{Amount, NumPeersExt, OutPoint, PeerId, Tiered}; +use fedimint_core::{Amount, NumPeersExt, OutPoint, PeerId, Tiered, TransactionId}; use fedimint_derive_secret::{ChildId, DerivableSecret}; use fedimint_logging::LOG_CLIENT_MODULE_MINT; use fedimint_mint_common::endpoint_constants::AWAIT_OUTPUT_OUTCOME_ENDPOINT; @@ -68,12 +68,15 @@ pub enum MintOutputStates { /// The issuance was completed successfully and the e-cash notes added to /// our wallet Succeeded(MintOutputStatesSucceeded), + /// Issuance request was created, we are waiting for blind signatures + CreatedMulti(MintOutputStatesCreatedMulti), } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintOutputCommon { pub(crate) operation_id: OperationId, - pub(crate) out_point: OutPoint, + pub(crate) txid: TransactionId, + pub(crate) out_idxs: std::ops::RangeInclusive, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] @@ -92,7 +95,10 @@ impl State for MintOutputStateMachine { ) -> Vec> { match &self.state { MintOutputStates::Created(created) => { - created.transitions(context, global_context, self.common) + created.transitions(context, global_context, &self.common) + } + MintOutputStates::CreatedMulti(created) => { + created.transitions(context, global_context, &self.common) } MintOutputStates::Aborted(_) | MintOutputStates::Failed(_) @@ -120,7 +126,7 @@ impl MintOutputStatesCreated { // TODO: make cheaper to clone (Arc?) context: &MintClientContext, global_context: &DynGlobalClientContext, - common: MintOutputCommon, + common: &MintOutputCommon, ) -> Vec> { let tbs_pks = context.tbs_pks.clone(); let client_ctx = context.client_ctx.clone(); @@ -128,14 +134,14 @@ impl MintOutputStatesCreated { vec![ // Check if transaction was rejected StateTransition::new( - Self::await_tx_rejected(global_context.clone(), common), + Self::await_tx_rejected(global_context.clone(), common.clone()), |_dbtx, (), state| Box::pin(async move { Self::transition_tx_rejected(&state) }), ), // Check for output outcome StateTransition::new( Self::await_outcome_ready( global_context.clone(), - common, + common.clone(), context.mint_decoder.clone(), self.amount, self.issuance_request.blinded_message(), @@ -155,11 +161,7 @@ impl MintOutputStatesCreated { } async fn await_tx_rejected(global_context: DynGlobalClientContext, common: MintOutputCommon) { - if global_context - .await_tx_accepted(common.out_point.txid) - .await - .is_err() - { + if global_context.await_tx_accepted(common.txid).await.is_err() { return; } std::future::pending::<()>().await; @@ -169,7 +171,7 @@ impl MintOutputStatesCreated { assert!(matches!(old_state.state, MintOutputStates::Created(_))); MintOutputStateMachine { - common: old_state.common, + common: old_state.common.clone(), state: MintOutputStates::Aborted(MintOutputStatesAborted), } } @@ -197,7 +199,10 @@ impl MintOutputStatesCreated { global_context.api().all_peers().to_num_peers(), ), AWAIT_OUTPUT_OUTCOME_ENDPOINT.to_owned(), - ApiRequestErased::new(common.out_point), + ApiRequestErased::new(OutPoint { + txid: common.txid, + out_idx: *common.out_idxs.start(), + }), ) .await { @@ -287,6 +292,189 @@ impl MintOutputStatesCreated { } } +/// See [`MintOutputStates`] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOutputStatesCreatedMulti { + pub(crate) issuance_requests: BTreeMap, +} + +impl MintOutputStatesCreatedMulti { + fn transitions( + &self, + // TODO: make cheaper to clone (Arc?) + context: &MintClientContext, + global_context: &DynGlobalClientContext, + common: &MintOutputCommon, + ) -> Vec> { + let tbs_pks = context.tbs_pks.clone(); + let client_ctx = context.client_ctx.clone(); + + vec![ + // Check if transaction was rejected + StateTransition::new( + Self::await_tx_rejected(global_context.clone(), common.clone()), + |_dbtx, (), state| Box::pin(async move { Self::transition_tx_rejected(&state) }), + ), + // Check for output outcome + StateTransition::new( + Self::await_outcome_ready( + global_context.clone(), + common.clone(), + context.mint_decoder.clone(), + self.issuance_requests.clone(), + context.peer_tbs_pks.clone(), + ), + move |dbtx, blinded_signature_shares, old_state| { + Box::pin(Self::transition_outcome_ready( + client_ctx.clone(), + dbtx, + blinded_signature_shares, + old_state, + tbs_pks.clone(), + )) + }, + ), + ] + } + + async fn await_tx_rejected(global_context: DynGlobalClientContext, common: MintOutputCommon) { + if global_context.await_tx_accepted(common.txid).await.is_err() { + return; + } + std::future::pending::<()>().await; + } + + fn transition_tx_rejected(old_state: &MintOutputStateMachine) -> MintOutputStateMachine { + assert!(matches!(old_state.state, MintOutputStates::CreatedMulti(_))); + + MintOutputStateMachine { + common: old_state.common.clone(), + state: MintOutputStates::Aborted(MintOutputStatesAborted), + } + } + + async fn await_outcome_ready( + global_context: DynGlobalClientContext, + common: MintOutputCommon, + module_decoder: Decoder, + issuance_requests: BTreeMap, + peer_tbs_pks: BTreeMap>, + ) -> BTreeMap> { + let mut ret = BTreeMap::new(); + // NOTE: We need a new api endpoint that can confirm multiple notes at once? + // --dpc + for (out_idx, (amount, issuance_request)) in issuance_requests { + let blinded_sig_share = fedimint_core::util::retry( + "await and fetch output sigs", + fedimint_core::util::backoff_util::custom_backoff(RETRY_DELAY, RETRY_DELAY, None), + || async { + let decoder = module_decoder.clone(); + let pks = peer_tbs_pks.clone(); + + Ok(global_context + .api() + .request_with_strategy( + // this query collects a threshold of 2f + 1 valid blind signature + // shares + FilterMapThreshold::new( + move |peer, outcome| { + verify_blind_share( + peer, + &outcome, + amount, + issuance_request.blinded_message(), + &decoder, + &pks, + ) + }, + global_context.api().all_peers().to_num_peers(), + ), + AWAIT_OUTPUT_OUTCOME_ENDPOINT.to_owned(), + ApiRequestErased::new(OutPoint { + txid: common.txid, + out_idx, + }), + ) + .await?) + }, + ) + .await + .expect("Will retry forever"); + + ret.insert(out_idx, blinded_sig_share); + } + + ret + } + + async fn transition_outcome_ready( + client_ctx: ClientContext, + dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, + blinded_signature_shares: BTreeMap>, + old_state: MintOutputStateMachine, + tbs_pks: Tiered, + ) -> MintOutputStateMachine { + // we combine the shares, finalize the issuance request with the blind signature + // and store the resulting note in the database + + let mut amount_total = Amount::ZERO; + let MintOutputStates::CreatedMulti(created) = old_state.state else { + panic!("Unexpected prior state") + }; + + for (out_idx, blinded_signature_shares) in blinded_signature_shares { + let agg_blind_signature = aggregate_signature_shares( + &blinded_signature_shares + .into_iter() + .map(|(peer, share)| (peer.to_usize() as u64 + 1, share)) + .collect(), + ); + + // this implies that the mint client config's public keys are inconsistent + let (amount, issuance_request) = + created.issuance_requests.get(&out_idx).expect("Must have"); + + let amount_key = tbs_pks.tier(amount).expect("Must have keys for any amount"); + + let spendable_note = issuance_request.finalize(agg_blind_signature); + + assert!(spendable_note.note().verify(*amount_key), "We checked all signature shares in the trigger future, so the combined signature has to be valid"); + + debug!(target: LOG_CLIENT_MODULE_MINT, amount = %amount, note=%spendable_note, "Adding new note from transaction output"); + + client_ctx + .log_event( + &mut dbtx.module_tx(), + NoteCreated { + nonce: spendable_note.nonce(), + }, + ) + .await; + + amount_total += *amount; + if let Some(note) = dbtx + .module_tx() + .insert_entry( + &NoteKey { + amount: *amount, + nonce: spendable_note.nonce(), + }, + &spendable_note.to_undecoded(), + ) + .await + { + error!(?note, "E-cash note was replaced in DB"); + } + } + MintOutputStateMachine { + common: old_state.common, + state: MintOutputStates::Succeeded(MintOutputStatesSucceeded { + amount: amount_total, + }), + } + } +} + /// # Panics /// If the given `outcome` is not a [`MintOutputOutcome::V0`] outcome. pub fn verify_blind_share( From 2a067936f1b0ecddfcbfeb2ec8dda7135916ffe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Wed, 6 Nov 2024 13:32:25 -0800 Subject: [PATCH 2/3] chore(client): parallize note verification --- Cargo.lock | 1 + Cargo.toml | 1 + fedimint-server/Cargo.toml | 2 +- modules/fedimint-mint-client/Cargo.toml | 1 + modules/fedimint-mint-client/src/output.rs | 49 +++++++++++++--------- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18f5e3e0cf9..b29103f3035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2905,6 +2905,7 @@ dependencies = [ "futures", "hex", "itertools 0.13.0", + "rayon", "serde", "serde-big-array", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 86a11d6efa7..71ef29eb5df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,6 +160,7 @@ ln-gateway = { package = "fedimint-ln-gateway", path = "./gateway/ln-gateway", v miniscript = "12.2.0" rand = "0.8.5" rand_chacha = "0.3.1" +rayon = "1.10.0" reqwest = { version = "0.12.9", features = [ "json", "rustls-tls", diff --git a/fedimint-server/Cargo.toml b/fedimint-server/Cargo.toml index c99bde32c94..9abb544a162 100644 --- a/fedimint-server/Cargo.toml +++ b/fedimint-server/Cargo.toml @@ -43,7 +43,7 @@ parity-scale-codec = "3.6.12" pin-project = "1.1.7" rand = { workspace = true } rand_chacha = { workspace = true } -rayon = "1.10.0" +rayon = { workspace = true } rcgen = "=0.13.1" serde = { workspace = true } serde_json = { workspace = true } diff --git a/modules/fedimint-mint-client/Cargo.toml b/modules/fedimint-mint-client/Cargo.toml index cbe44d986ed..327c222a5b4 100644 --- a/modules/fedimint-mint-client/Cargo.toml +++ b/modules/fedimint-mint-client/Cargo.toml @@ -43,6 +43,7 @@ fedimint-mint-common = { workspace = true } futures = { workspace = true } hex = { workspace = true } itertools = { workspace = true } +rayon = { workspace = true } serde = { workspace = true } serde-big-array = { workspace = true } serde_json = { workspace = true } diff --git a/modules/fedimint-mint-client/src/output.rs b/modules/fedimint-mint-client/src/output.rs index 9c0eb0d46e3..bf4ca796761 100644 --- a/modules/fedimint-mint-client/src/output.rs +++ b/modules/fedimint-mint-client/src/output.rs @@ -20,6 +20,7 @@ use fedimint_derive_secret::{ChildId, DerivableSecret}; use fedimint_logging::LOG_CLIENT_MODULE_MINT; use fedimint_mint_common::endpoint_constants::AWAIT_OUTPUT_OUTCOME_ENDPOINT; use fedimint_mint_common::{BlindNonce, MintOutputOutcome, Nonce}; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator as _, ParallelIterator as _}; use serde::{Deserialize, Serialize}; use tbs::{ aggregate_signature_shares, blind_message, unblind_signature, AggregatePublicKey, @@ -359,8 +360,8 @@ impl MintOutputStatesCreatedMulti { module_decoder: Decoder, issuance_requests: BTreeMap, peer_tbs_pks: BTreeMap>, - ) -> BTreeMap> { - let mut ret = BTreeMap::new(); + ) -> Vec<(u64, BTreeMap)> { + let mut ret = vec![]; // NOTE: We need a new api endpoint that can confirm multiple notes at once? // --dpc for (out_idx, (amount, issuance_request)) in issuance_requests { @@ -401,7 +402,7 @@ impl MintOutputStatesCreatedMulti { .await .expect("Will retry forever"); - ret.insert(out_idx, blinded_sig_share); + ret.push((out_idx, blinded_sig_share)); } ret @@ -410,7 +411,7 @@ impl MintOutputStatesCreatedMulti { async fn transition_outcome_ready( client_ctx: ClientContext, dbtx: &mut ClientSMDatabaseTransaction<'_, '_>, - blinded_signature_shares: BTreeMap>, + blinded_signature_shares: Vec<(u64, BTreeMap)>, old_state: MintOutputStateMachine, tbs_pks: Tiered, ) -> MintOutputStateMachine { @@ -422,24 +423,34 @@ impl MintOutputStatesCreatedMulti { panic!("Unexpected prior state") }; - for (out_idx, blinded_signature_shares) in blinded_signature_shares { - let agg_blind_signature = aggregate_signature_shares( - &blinded_signature_shares - .into_iter() - .map(|(peer, share)| (peer.to_usize() as u64 + 1, share)) - .collect(), - ); + let mut spendable_notes: Vec<(Amount, SpendableNote)> = vec![]; - // this implies that the mint client config's public keys are inconsistent - let (amount, issuance_request) = - created.issuance_requests.get(&out_idx).expect("Must have"); + // Note verification is relatively slow and CPU-bound, so parallelize them + blinded_signature_shares + .into_par_iter() + .map(|(out_idx, blinded_signature_shares)| { + let agg_blind_signature = aggregate_signature_shares( + &blinded_signature_shares + .into_iter() + .map(|(peer, share)| (peer.to_usize() as u64 + 1, share)) + .collect(), + ); - let amount_key = tbs_pks.tier(amount).expect("Must have keys for any amount"); + // this implies that the mint client config's public keys are inconsistent + let (amount, issuance_request) = + created.issuance_requests.get(&out_idx).expect("Must have"); - let spendable_note = issuance_request.finalize(agg_blind_signature); + let amount_key = tbs_pks.tier(amount).expect("Must have keys for any amount"); - assert!(spendable_note.note().verify(*amount_key), "We checked all signature shares in the trigger future, so the combined signature has to be valid"); + let spendable_note = issuance_request.finalize(agg_blind_signature); + assert!(spendable_note.note().verify(*amount_key), "We checked all signature shares in the trigger future, so the combined signature has to be valid"); + + (*amount, spendable_note) + }) + .collect_into_vec(&mut spendable_notes); + + for (amount, spendable_note) in spendable_notes { debug!(target: LOG_CLIENT_MODULE_MINT, amount = %amount, note=%spendable_note, "Adding new note from transaction output"); client_ctx @@ -451,12 +462,12 @@ impl MintOutputStatesCreatedMulti { ) .await; - amount_total += *amount; + amount_total += amount; if let Some(note) = dbtx .module_tx() .insert_entry( &NoteKey { - amount: *amount, + amount, nonce: spendable_note.nonce(), }, &spendable_note.to_undecoded(), From 8d156fd39049203fc134c2debe99e1182c3a6d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Mon, 4 Nov 2024 16:39:42 -0800 Subject: [PATCH 3/3] chore(client): migrate old client mint output state machines --- modules/fedimint-mint-client/src/client_db.rs | 13 +++++++++++++ modules/fedimint-mint-client/src/output.rs | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/modules/fedimint-mint-client/src/client_db.rs b/modules/fedimint-mint-client/src/client_db.rs index 4273e967942..3e9cf839863 100644 --- a/modules/fedimint-mint-client/src/client_db.rs +++ b/modules/fedimint-mint-client/src/client_db.rs @@ -13,6 +13,7 @@ use strum_macros::EnumIter; use crate::backup::recovery::MintRecoveryState; use crate::input::{MintInputCommon, MintInputStateMachine, MintInputStateMachineV1}; use crate::oob::{MintOOBStateMachine, MintOOBStateMachineV1, MintOOBStates, MintOOBStatesV1}; +use crate::output::{MintOutputCommon, MintOutputStateMachine, MintOutputStateMachineV1}; use crate::{MintClientStateMachines, SpendableNoteUndecoded}; #[repr(u8)] @@ -126,6 +127,18 @@ pub(crate) fn migrate_state_to_v2( let mint_client_state_machine_variant = u16::consensus_decode(cursor, &decoders)?; let new_mint_state_machine = match mint_client_state_machine_variant { + 0 => { + let old_state = MintOutputStateMachineV1::consensus_decode(cursor, &decoders)?; + MintClientStateMachines::Output(MintOutputStateMachine { + common: MintOutputCommon { + operation_id: old_state.common.operation_id, + txid: old_state.common.out_point.txid, + out_idxs: old_state.common.out_point.out_idx + ..=old_state.common.out_point.out_idx, + }, + state: old_state.state, + }) + } 1 => { let old_state = MintInputStateMachineV1::consensus_decode(cursor, &decoders)?; diff --git a/modules/fedimint-mint-client/src/output.rs b/modules/fedimint-mint-client/src/output.rs index bf4ca796761..265ce8084e4 100644 --- a/modules/fedimint-mint-client/src/output.rs +++ b/modules/fedimint-mint-client/src/output.rs @@ -73,6 +73,12 @@ pub enum MintOutputStates { CreatedMulti(MintOutputStatesCreatedMulti), } +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOutputCommonV1 { + pub(crate) operation_id: OperationId, + pub(crate) out_point: OutPoint, +} + #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintOutputCommon { pub(crate) operation_id: OperationId, @@ -80,6 +86,12 @@ pub struct MintOutputCommon { pub(crate) out_idxs: std::ops::RangeInclusive, } +#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] +pub struct MintOutputStateMachineV1 { + pub(crate) common: MintOutputCommonV1, + pub(crate) state: MintOutputStates, +} + #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)] pub struct MintOutputStateMachine { pub(crate) common: MintOutputCommon,