diff --git a/mutiny-core/src/federation.rs b/mutiny-core/src/federation.rs index 817a6e09b..45d85c55f 100644 --- a/mutiny-core/src/federation.rs +++ b/mutiny-core/src/federation.rs @@ -27,6 +27,7 @@ use bitcoin::{ Address, Network, Txid, }; use core::fmt; +use esplora_client::AsyncClient; use fedimint_bip39::Bip39RootSecretStrategy; use fedimint_client::{ derivable_secret::DerivableSecret, @@ -194,6 +195,7 @@ pub(crate) struct FederationClient { #[allow(dead_code)] fedimint_storage: FedimintStorage, gateway: Arc>>, + esplora: Arc, network: Network, stop: Arc, pub(crate) logger: Arc, @@ -206,6 +208,7 @@ impl FederationClient { federation_code: InviteCode, xprivkey: ExtendedPrivKey, storage: S, + esplora: Arc, network: Network, stop: Arc, logger: Arc, @@ -335,6 +338,7 @@ impl FederationClient { storage, logger, invite_code: federation_code, + esplora, network, stop, gateway, @@ -421,6 +425,7 @@ impl FederationClient { entry, operation_id, self.fedimint_client.clone(), + self.esplora.clone(), self.logger.clone(), self.stop.clone(), self.storage.clone(), @@ -471,6 +476,7 @@ impl FederationClient { let fedimint_client_clone = self.fedimint_client.clone(); let logger_clone = self.logger.clone(); let storage_clone = self.storage.clone(); + let esplora_clone = self.esplora.clone(); let stop = self.stop.clone(); spawn(async move { let operation = fedimint_client_clone @@ -483,6 +489,7 @@ impl FederationClient { operation, id, fedimint_client_clone, + esplora_clone, logger_clone, stop, storage_clone, @@ -630,6 +637,7 @@ impl FederationClient { let fedimint_client_clone = self.fedimint_client.clone(); let logger_clone = self.logger.clone(); let storage_clone = self.storage.clone(); + let esplora_clone = self.esplora.clone(); let stop = self.stop.clone(); spawn(async move { let operation = fedimint_client_clone @@ -642,6 +650,7 @@ impl FederationClient { operation, id, fedimint_client_clone, + esplora_clone, logger_clone, stop, storage_clone, @@ -708,6 +717,7 @@ impl FederationClient { op_id, self.fedimint_client.clone(), self.storage.clone(), + self.esplora.clone(), Some(DEFAULT_PAYMENT_TIMEOUT * 1_000), self.stop.clone(), ) @@ -809,6 +819,7 @@ fn subscribe_operation_ext( entry: OperationLogEntry, operation_id: OperationId, fedimint_client: ClientHandleArc, + esplora: Arc, logger: Arc, stop: Arc, storage: S, @@ -820,6 +831,7 @@ fn subscribe_operation_ext( operation_id, fedimint_client, storage, + esplora, None, stop, ) @@ -940,6 +952,7 @@ async fn process_operation_until_timeout( operation_id: OperationId, fedimint_client: ClientHandleArc, storage: S, + esplora: Arc, timeout: Option, stop: Arc, ) { @@ -1073,6 +1086,7 @@ async fn process_operation_until_timeout( fee.amount(), operation_id, storage, + esplora, timeout, stop, logger, @@ -1206,6 +1220,7 @@ async fn process_onchain_withdraw_outcome( fee: fedimint_ln_common::bitcoin::Amount, operation_id: OperationId, storage: S, + esplora: Arc, timeout: Option, stop: Arc, logger: Arc, @@ -1268,8 +1283,10 @@ async fn process_onchain_withdraw_outcome( }, } - // TODO we need to get confirmations for this txid and update - break; + // we need to get confirmations for this txid and update + subscribe_onchain_confirmation_check(storage.clone(), esplora.clone(), txid, updated_transaction_details, stop, logger.clone()).await; + + break }, WithdrawState::Failed(e) => { // TODO delete @@ -1299,6 +1316,49 @@ async fn process_onchain_withdraw_outcome( } } +async fn subscribe_onchain_confirmation_check( + storage: S, + esplora: Arc, + txid: Txid, + mut transaction_details: TransactionDetails, + stop: Arc, + logger: Arc, +) { + spawn(async move { + loop { + if stop.load(Ordering::Relaxed) { + break; + }; + + match esplora.get_tx_status(&txid).await { + Ok(s) => { + if s.confirmed { + log_info!(logger, "Transaction confirmed"); + transaction_details.confirmation_time = ConfirmationTime::Confirmed { + height: s.block_height.expect("confirmed"), + time: now().as_secs(), + }; + match persist_transaction_details(&storage, &transaction_details) { + Ok(_) => { + log_info!(logger, "Transaction updated"); + break; + } + Err(e) => { + log_error!(logger, "Error updating transaction: {e}"); + } + } + } + } + Err(e) => { + log_error!(logger, "Error updating transaction: {e}"); + } + } + + sleep(5_000).await; + } + }); +} + async fn process_onchain_deposit_outcome( stream_or_outcome: UpdateStreamOrOutcome, original_transaction_details: Option, diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index f6dd31b0b..b0623ed49 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -46,12 +46,6 @@ mod test_utils; pub use crate::gossip::{GOSSIP_SYNC_TIME_KEY, NETWORK_GRAPH_KEY, PROB_SCORER_KEY}; pub use crate::keymanager::generate_seed; pub use crate::ldkstorage::{CHANNEL_CLOSURE_PREFIX, CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY}; -use crate::storage::{ - get_payment_hash_from_key, get_transaction_details, list_payment_info, persist_payment_info, - update_nostr_contact_list, IndexItem, MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, - NEED_FULL_SYNC_KEY, ONCHAIN_PREFIX, PAYMENT_INBOUND_PREFIX_KEY, PAYMENT_OUTBOUND_PREFIX_KEY, - SUBSCRIPTION_TIMESTAMP, TRANSACTION_DETAILS_PREFIX_KEY, -}; use crate::utils::spawn; use crate::{auth::MutinyAuthClient, hermes::HermesClient, logging::MutinyLogger}; use crate::{blindauth::BlindAuthClient, cashu::CashuHttpClient}; @@ -83,6 +77,15 @@ use crate::{ storage::get_invoice_by_hash, }; use crate::{nostr::NostrManager, utils::sleep}; +use crate::{ + onchain::get_esplora_url, + storage::{ + get_payment_hash_from_key, get_transaction_details, list_payment_info, + persist_payment_info, update_nostr_contact_list, IndexItem, MutinyStorage, DEVICE_ID_KEY, + EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY, ONCHAIN_PREFIX, PAYMENT_INBOUND_PREFIX_KEY, + PAYMENT_OUTBOUND_PREFIX_KEY, SUBSCRIPTION_TIMESTAMP, TRANSACTION_DETAILS_PREFIX_KEY, + }, +}; use ::nostr::nips::nip47::Method; use ::nostr::nips::nip57; #[cfg(target_arch = "wasm32")] @@ -101,6 +104,7 @@ use bitcoin::{ use bitcoin::{bip32::ExtendedPrivKey, Transaction}; use bitcoin::{hashes::sha256, Network, Txid}; use bitcoin::{hashes::Hash, Address}; +use esplora_client::AsyncClient; use fedimint_core::{api::InviteCode, config::FederationId}; use futures::{pin_mut, select, FutureExt}; use futures_util::join; @@ -924,11 +928,16 @@ impl MutinyWalletBuilder { } }); + let esplora_server_url = get_esplora_url(network, config.user_esplora_url.clone()); + let esplora = esplora_client::Builder::new(&esplora_server_url).build_async()?; + let esplora = Arc::new(esplora); + let start = Instant::now(); let mut nm_builder = NodeManagerBuilder::new(self.xprivkey, self.storage.clone()) .with_config(config.clone()); nm_builder.with_logger(logger.clone()); + nm_builder.with_esplora(esplora.clone()); let node_manager = Arc::new(nm_builder.build().await?); log_trace!( @@ -976,6 +985,7 @@ impl MutinyWalletBuilder { federation_storage.clone(), &config, self.storage.clone(), + esplora.clone(), stop.clone(), &logger, ) @@ -1164,6 +1174,7 @@ impl MutinyWalletBuilder { subscription_client, blind_auth_client, hermes_client, + esplora, auth, stop, logger, @@ -1236,6 +1247,7 @@ pub struct MutinyWallet { subscription_client: Option>, blind_auth_client: Option>>, hermes_client: Option>>, + esplora: Arc, pub stop: Arc, pub logger: Arc, network: Network, @@ -2623,6 +2635,7 @@ impl MutinyWallet { self.federation_storage.clone(), self.federations.clone(), self.hermes_client.clone(), + self.esplora.clone(), federation_code, self.stop.clone(), ) @@ -3280,6 +3293,7 @@ async fn create_federations( federation_storage: FederationStorage, c: &MutinyWalletConfig, storage: S, + esplora: Arc, stop: Arc, logger: &Arc, ) -> Result>>>>, MutinyError> { @@ -3290,6 +3304,7 @@ async fn create_federations( federation_index.federation_code, c.xprivkey, storage.clone(), + esplora.clone(), c.network, stop.clone(), logger.clone(), @@ -3314,6 +3329,7 @@ pub(crate) async fn create_new_federation( federation_storage: Arc>, federations: Arc>>>>, hermes_client: Option>>, + esplora: Arc, federation_code: InviteCode, stop: Arc, ) -> Result { @@ -3343,6 +3359,7 @@ pub(crate) async fn create_new_federation( federation_code.clone(), xprivkey, storage.clone(), + esplora, network, stop.clone(), logger.clone(), diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index 9251d60b0..b35c0b2b8 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -232,6 +232,7 @@ pub struct NodeBalance { pub struct NodeManagerBuilder { xprivkey: ExtendedPrivKey, storage: S, + esplora: Option>, config: Option, stop: Option>, logger: Option>, @@ -242,6 +243,7 @@ impl NodeManagerBuilder { NodeManagerBuilder:: { xprivkey, storage, + esplora: None, config: None, stop: None, logger: None, @@ -257,6 +259,10 @@ impl NodeManagerBuilder { self.stop = Some(stop); } + pub fn with_esplora(&mut self, esplora: Arc) { + self.esplora = Some(esplora); + } + pub fn with_logger(&mut self, logger: Arc) { self.logger = Some(logger); } @@ -271,6 +277,13 @@ impl NodeManagerBuilder { .map_or_else(|| Err(MutinyError::InvalidArgumentsError), Ok)?; let logger = self.logger.unwrap_or(Arc::new(MutinyLogger::default())); let stop = self.stop.unwrap_or(Arc::new(AtomicBool::new(false))); + let esplora = if let Some(e) = self.esplora { + e + } else { + let esplora_server_url = get_esplora_url(c.network, c.user_esplora_url); + let esplora = Builder::new(&esplora_server_url).build_async()?; + Arc::new(esplora) + }; #[cfg(target_arch = "wasm32")] let websocket_proxy_addr = c @@ -280,14 +293,11 @@ impl NodeManagerBuilder { let start = Instant::now(); log_info!(logger, "Building node manager components"); - let esplora_server_url = get_esplora_url(c.network, c.user_esplora_url); - let esplora = Builder::new(&esplora_server_url).build_async()?; let tx_sync = Arc::new(EsploraSyncClient::from_client( - esplora.clone(), + esplora.as_ref().clone(), logger.clone(), )); - let esplora = Arc::new(esplora); let fee_estimator = Arc::new(MutinyFeeEstimator::new( self.storage.clone(), esplora.clone(),