-
Notifications
You must be signed in to change notification settings - Fork 35
wip(feat): reissue ecash notes from OOBNotes
#1037
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ debug/ | |
| target/ | ||
| .vim/ | ||
| .direnv | ||
| .editorconfig | ||
|
|
||
| # These are backup files generated by rustfmt | ||
| **/*.rs.bk | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ use crate::{ | |
| get_payment_info, list_payment_info, persist_payment_info, MutinyStorage, VersionedValue, | ||
| }, | ||
| utils::sleep, | ||
| HTLCStatus, MutinyInvoice, DEFAULT_PAYMENT_TIMEOUT, | ||
| HTLCStatus, MutinyInvoice, DEFAULT_PAYMENT_TIMEOUT, DEFAULT_REISSUE_TIMEOUT, | ||
| }; | ||
| use async_trait::async_trait; | ||
| use bip39::Mnemonic; | ||
|
|
@@ -52,7 +52,7 @@ use fedimint_ln_client::{ | |
| }; | ||
| use fedimint_ln_common::lightning_invoice::RoutingFees; | ||
| use fedimint_ln_common::LightningCommonInit; | ||
| use fedimint_mint_client::MintClientInit; | ||
| use fedimint_mint_client::{MintClientInit, MintClientModule, OOBNotes, ReissueExternalNotesState}; | ||
| use fedimint_wallet_client::{WalletClientInit, WalletClientModule}; | ||
| use futures::future::{self}; | ||
| use futures_util::{pin_mut, StreamExt}; | ||
|
|
@@ -609,6 +609,26 @@ impl<S: MutinyStorage> FederationClient<S> { | |
| } | ||
| } | ||
|
|
||
| pub(crate) async fn reissue(&self, oob_notes: OOBNotes) -> Result<(), MutinyError> { | ||
| // Get the `MintClientModule` | ||
| let mint_module = self.fedimint_client.get_first_module::<MintClientModule>(); | ||
|
|
||
| // Reissue `OOBNotes` | ||
| let operation_id = mint_module.reissue_external_notes(oob_notes, ()).await?; | ||
|
|
||
| // TODO: (@leonardo) re-think about the results and errors that we need/want | ||
| match process_reissue_outcome(&mint_module, operation_id, self.logger.clone()).await? { | ||
| ReissueExternalNotesState::Created | ReissueExternalNotesState::Failed(_) => { | ||
| log_trace!(self.logger, "re-issuance of OOBNotes failed!"); | ||
| Err(MutinyError::FedimintReissueFailed) | ||
| } | ||
| _ => { | ||
| log_trace!(self.logger, "re-issuance of OOBNotes was successful!"); | ||
| Ok(()) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub async fn get_mutiny_federation_identity(&self) -> FederationIdentity { | ||
| let gateway_fees = self.gateway_fee().await.ok(); | ||
|
|
||
|
|
@@ -866,6 +886,53 @@ where | |
| invoice | ||
| } | ||
|
|
||
| async fn process_reissue_outcome( | ||
| mint_module: &MintClientModule, | ||
| operation_id: OperationId, | ||
| logger: Arc<MutinyLogger>, | ||
| ) -> Result<ReissueExternalNotesState, MutinyError> { | ||
| // Subscribe/Process the outcome based on `ReissueExternalNotesState` | ||
| let stream_or_outcome = mint_module | ||
| .subscribe_reissue_external_notes(operation_id) | ||
| .await | ||
| .map_err(MutinyError::Other)?; | ||
|
|
||
| match stream_or_outcome { | ||
| UpdateStreamOrOutcome::Outcome(outcome) => { | ||
| log_trace!(logger, "outcome received {:?}", outcome); | ||
| Ok(outcome) | ||
| } | ||
| UpdateStreamOrOutcome::UpdateStream(mut stream) => { | ||
| let timeout = DEFAULT_REISSUE_TIMEOUT; | ||
| let timeout_fut = sleep(timeout as i32); | ||
| pin_mut!(timeout_fut); | ||
|
|
||
| log_trace!(logger, "started timeout future {:?}", timeout); | ||
|
|
||
| while let future::Either::Left((outcome_opt, _)) = | ||
| future::select(stream.next(), &mut timeout_fut).await | ||
| { | ||
| if let Some(outcome) = outcome_opt { | ||
| log_trace!(logger, "streamed outcome received {:?}", outcome); | ||
|
|
||
| match outcome { | ||
| ReissueExternalNotesState::Failed(_) | ReissueExternalNotesState::Done => { | ||
| log_trace!( | ||
| logger, | ||
| "streamed outcome received is final {:?}, returning", | ||
| outcome | ||
| ); | ||
| return Ok(outcome); | ||
| } | ||
| _ => { /* ignore and continue */ } | ||
|
||
| } | ||
| }; | ||
| } | ||
| Err(MutinyError::FedimintReissueFailed) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct FedimintStorage<S: MutinyStorage> { | ||
| pub(crate) storage: S, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,6 @@ | |
| clippy::arc_with_non_send_sync, | ||
| type_alias_bounds | ||
| )] | ||
| extern crate core; | ||
|
|
||
| pub mod auth; | ||
| mod cashu; | ||
|
|
@@ -85,6 +84,7 @@ use bitcoin::hashes::Hash; | |
| use bitcoin::secp256k1::{PublicKey, ThirtyTwoByteHash}; | ||
| use bitcoin::{hashes::sha256, Network}; | ||
| use fedimint_core::{api::InviteCode, config::FederationId}; | ||
| use fedimint_mint_client::OOBNotes; | ||
| use futures::{pin_mut, select, FutureExt}; | ||
| use futures_util::join; | ||
| use hex_conservative::{DisplayHex, FromHex}; | ||
|
|
@@ -119,6 +119,7 @@ use crate::utils::parse_profile_metadata; | |
| use mockall::{automock, predicate::*}; | ||
|
|
||
| const DEFAULT_PAYMENT_TIMEOUT: u64 = 30; | ||
| const DEFAULT_REISSUE_TIMEOUT: u64 = 50; | ||
|
||
| const MAX_FEDERATION_INVOICE_AMT: u64 = 200_000; | ||
| const SWAP_LABEL: &str = "SWAP"; | ||
| const MELT_CASHU_TOKEN: &str = "Cashu Token Melt"; | ||
|
|
@@ -1362,6 +1363,37 @@ impl<S: MutinyStorage> MutinyWallet<S> { | |
| }) | ||
| } | ||
|
|
||
| pub async fn reissue_oob_notes(&self, oob_notes: OOBNotes) -> Result<(), MutinyError> { | ||
| let federation_lock = self.federations.read().await; | ||
| let federation_ids = self.list_federation_ids().await?; | ||
|
|
||
| let maybe_federation_id = federation_ids | ||
| .iter() | ||
| .find(|id| id.to_prefix() == oob_notes.federation_id_prefix()); | ||
|
|
||
| if let Some(fed_id) = maybe_federation_id { | ||
| log_trace!(self.logger, "found federation_id {:?}", fed_id); | ||
|
|
||
| let fedimint_client = federation_lock.get(fed_id).ok_or(MutinyError::NotFound)?; | ||
| log_trace!( | ||
| self.logger, | ||
| "got fedimint client for federation_id {:?}", | ||
| fed_id | ||
| ); | ||
|
|
||
| fedimint_client.reissue(oob_notes).await?; | ||
| log_trace!( | ||
| self.logger, | ||
| "successfully reissued for federation_id {:?}", | ||
| fed_id | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } else { | ||
| Err(MutinyError::NotFound) | ||
| } | ||
AnthonyRonning marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// Estimate the fee before trying to sweep from federation | ||
| pub async fn estimate_sweep_federation_fee( | ||
| &self, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why return error on created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to proceed with this, but if the stream is already finished and the final result is still Created, it didn't get processed by the federation without a real guaranteeing it will.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll need to check in the discord about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK there is no known error it would still be stuck at created even after all the stream is consumed, but still needs to handle the variant 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benthecarman What do you think we should do here with Created variant?
It's not supposed to have that after the whole stream is consumed, so still handle as failure/error ? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in
process_reissue_outcomeyou only return the outcome if it isFailedorDoneso you should just handle those two here, and leave the rest in the_ =>case where you can throw an error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdyt about eb59a8b