From 22932d2835e713e5922681502c6ebbfb89c5a609 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Thu, 27 Jul 2023 09:48:35 +0200 Subject: [PATCH] fix: Wait for outbound capacity before paying jit channel open fees --- crates/tests-e2e/src/setup.rs | 5 +- crates/tests-e2e/tests/basic.rs | 13 +-- .../receive_payment_when_open_position.rs | 6 +- mobile/native/src/api.rs | 2 - mobile/native/src/channel_fee.rs | 95 ++++++++++++++----- mobile/native/src/ln_dlc/mod.rs | 5 + 6 files changed, 83 insertions(+), 43 deletions(-) diff --git a/crates/tests-e2e/src/setup.rs b/crates/tests-e2e/src/setup.rs index 22529d406..e86be0ddd 100644 --- a/crates/tests-e2e/src/setup.rs +++ b/crates/tests-e2e/src/setup.rs @@ -54,9 +54,6 @@ impl TestSetup { .await .expect("to be able to fund"); - // FIXME: Waiting here on >= as this test run on the CI can't find a route when trying to - // pay immediately after claiming a received payment. - // See: https://github.com/get10101/10101/issues/883 let ln_balance = app .rx .wallet_info() @@ -70,7 +67,7 @@ impl TestSetup { .expect("have wallet_info") .balances .lightning - >= funded_amount + == funded_amount ); Self { app, coordinator } diff --git a/crates/tests-e2e/tests/basic.rs b/crates/tests-e2e/tests/basic.rs index ba7813f85..3e3e55005 100644 --- a/crates/tests-e2e/tests/basic.rs +++ b/crates/tests-e2e/tests/basic.rs @@ -1,6 +1,4 @@ use anyhow::Result; -use assertables::assert_ge; -use assertables::assert_ge_as_result; use bitcoin::Address; use bitcoin::Amount; use std::str::FromStr; @@ -43,11 +41,10 @@ async fn app_can_be_funded_with_lnd_faucet() -> Result<()> { assert_eq!(app.rx.wallet_info().unwrap().balances.on_chain, 0); - // FIXME: Asserting here on >= as this test run on the CI can't find a route when trying to pay - // immediately after claiming a received payment. - // See: https://github.com/get10101/10101/issues/883 - let ln_balance = app.rx.wallet_info().unwrap().balances.lightning; - tracing::info!(%funded_amount, %ln_balance, "Successfully funded app with faucet"); - assert_ge!(ln_balance, funded_amount); + tracing::info!(%funded_amount, "Successfully funded app with faucet"); + assert_eq!( + app.rx.wallet_info().unwrap().balances.lightning, + funded_amount + ); Ok(()) } diff --git a/crates/tests-e2e/tests/receive_payment_when_open_position.rs b/crates/tests-e2e/tests/receive_payment_when_open_position.rs index a911a2a45..9af414407 100644 --- a/crates/tests-e2e/tests/receive_payment_when_open_position.rs +++ b/crates/tests-e2e/tests/receive_payment_when_open_position.rs @@ -1,5 +1,4 @@ use native::api; -use tests_e2e::fund::FUNDING_TRANSACTION_FEES; use tests_e2e::setup; use tests_e2e::wait_until; use tokio::task::spawn_blocking; @@ -26,8 +25,5 @@ async fn can_receive_payment_with_open_position() { let ln_balance = app.rx.wallet_info().unwrap().balances.lightning; tracing::info!(%ln_balance, %ln_balance_before, %invoice_amount, "Lightning balance increased"); - // FIXME: Change this assertion when the reason why we're being charged - // transaction funding fees now is found. - // See: https://github.com/get10101/10101/issues/883 - assert!(ln_balance >= ln_balance_before + invoice_amount - FUNDING_TRANSACTION_FEES); + assert_eq!(ln_balance, ln_balance_before + invoice_amount); } diff --git a/mobile/native/src/api.rs b/mobile/native/src/api.rs index 462fa71d1..eae549fbf 100644 --- a/mobile/native/src/api.rs +++ b/mobile/native/src/api.rs @@ -1,5 +1,4 @@ use crate::calculations; -use crate::channel_fee::ChannelFeePaymentSubscriber; use crate::commons::api::ChannelInfo; use crate::commons::api::Price; use crate::config; @@ -231,7 +230,6 @@ pub fn run( db::init_db(&app_dir, get_network())?; let runtime = ln_dlc::get_or_create_tokio_runtime()?; ln_dlc::run(app_dir, seed_dir, runtime)?; - event::subscribe(ChannelFeePaymentSubscriber::new()); let (_health, tx) = health::Health::new(config, runtime); diff --git a/mobile/native/src/channel_fee.rs b/mobile/native/src/channel_fee.rs index ae9a5fab5..81c5b8211 100644 --- a/mobile/native/src/channel_fee.rs +++ b/mobile/native/src/channel_fee.rs @@ -5,21 +5,24 @@ use crate::event::EventInternal; use crate::event::EventType; use crate::ln_dlc; use anyhow::anyhow; +use anyhow::bail; use anyhow::Context; use anyhow::Result; use bitcoin::Txid; -use lightning_invoice::Invoice; +use ln_dlc_node::node::rust_dlc_manager::subchannel::LNChannelManager; use ln_dlc_node::node::rust_dlc_manager::ChannelId; +use ln_dlc_node::node::ChannelManager; use serde::Deserialize; use serde::Serialize; -use std::str::FromStr; use std::sync::Arc; use std::sync::Mutex; +use std::time::Duration; use tokio::runtime::Handle; #[derive(Clone)] pub struct ChannelFeePaymentSubscriber { - pub open_channel_tx: Arc>>, + pub open_channel_info: Arc>>, + pub channel_manager: Arc, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -31,9 +34,7 @@ pub struct EsploraTransaction { impl Subscriber for ChannelFeePaymentSubscriber { fn notify(&self, event: &EventInternal) { let result = match event { - EventInternal::ChannelReady(channel_id) => { - self.register_funding_transaction(channel_id) - } + EventInternal::ChannelReady(channel_id) => self.register_channel_open_info(channel_id), EventInternal::PaymentClaimed(amount_msats) => { self.pay_funding_transaction_fees(*amount_msats) } @@ -51,24 +52,39 @@ impl Subscriber for ChannelFeePaymentSubscriber { } impl ChannelFeePaymentSubscriber { - pub fn new() -> Self { + pub fn new(channel_manager: Arc) -> Self { Self { - open_channel_tx: Arc::new(Mutex::new(None)), + open_channel_info: Arc::new(Mutex::new(None)), + channel_manager, } } /// Attempts to pay the transaction fees for opening an inbound channel. fn pay_funding_transaction_fees(&self, amount_msats: u64) -> Result<()> { - let transaction = match self.get_funding_transaction() { - Some(transaction) => transaction, + let (channel_id, transaction) = match self.get_open_channel_info() { + Some((channel_id, transaction)) => (channel_id, transaction), None => { tracing::debug!("No pending funding transaction found!"); return Ok(()); } }; - tracing::debug!("Trying to pay channel opening fees of {}", transaction.fee); let funding_tx_fees_msats = (transaction.fee * 1000) as u64; + + tokio::task::block_in_place(|| { + tracing::debug!( + channel_id = hex::encode(channel_id), + funding_tx_fees_msats, + "Waiting for outbound capacity on channel to pay jit channel opening fee.", + ); + Handle::current() + .block_on(self.wait_for_outbound_capacity(channel_id, funding_tx_fees_msats)) + })?; + + tracing::debug!( + "Trying to pay channel opening fees of {} sats", + transaction.fee + ); let funding_txid = transaction.txid; if funding_tx_fees_msats > amount_msats { @@ -82,13 +98,10 @@ impl ChannelFeePaymentSubscriber { )) })?; - let invoice = Invoice::from_str(&invoice_str).context("Could not parse Invoice string")?; - let _payment_hash = invoice.payment_hash(); - match ln_dlc::send_payment(&invoice_str) { Ok(_) => { // unset the funding transaction marking it as being paid. - self.unset_funding_transaction(); + self.unset_open_channel_info(); tracing::info!("Successfully triggered funding transaction fees payment of {funding_tx_fees_msats} msats to {}", config::get_coordinator_info().pubkey); } Err(e) => { @@ -100,7 +113,7 @@ impl ChannelFeePaymentSubscriber { } /// Register jit channel opening transaction for fee payment - fn register_funding_transaction(&self, channel_id: &ChannelId) -> Result<()> { + fn register_channel_open_info(&self, channel_id: &ChannelId) -> Result<()> { let channel_id_as_str = hex::encode(channel_id); tracing::debug!("Received new inbound channel with id {channel_id_as_str}"); @@ -110,30 +123,64 @@ impl ChannelFeePaymentSubscriber { Handle::current().block_on(fetch_funding_transaction(txid)) })?; tracing::debug!("Successfully fetched transaction fees of {} for new inbound channel with id {channel_id_as_str}", transaction.fee); - self.set_funding_transaction(transaction); + self.set_open_channel_info(channel_id, transaction); Ok(()) } - fn set_funding_transaction(&self, transaction: EsploraTransaction) { + fn set_open_channel_info(&self, channel_id: &ChannelId, transaction: EsploraTransaction) { *self - .open_channel_tx + .open_channel_info .lock() - .expect("Mutex to not be poisoned") = Some(transaction); + .expect("Mutex to not be poisoned") = Some((*channel_id, transaction)); } - fn unset_funding_transaction(&self) { + fn unset_open_channel_info(&self) { *self - .open_channel_tx + .open_channel_info .lock() .expect("Mutex to not be poisoned") = None; } - fn get_funding_transaction(&self) -> Option { - self.open_channel_tx + fn get_open_channel_info(&self) -> Option<(ChannelId, EsploraTransaction)> { + self.open_channel_info .lock() .expect("Mutex to not be poisoned") .clone() } + + async fn wait_for_outbound_capacity( + &self, + channel_id: ChannelId, + funding_tx_fees_msats: u64, + ) -> Result<()> { + tokio::time::timeout(Duration::from_secs(5), async { + loop { + let channel_details = match self + .channel_manager + .get_channel_details(&channel_id) { + Some(channel_details) => channel_details, + None => { + bail!("Could not find channel details for {}", hex::encode(channel_id)); + }, + }; + + if channel_details.outbound_capacity_msat >= funding_tx_fees_msats { + tracing::debug!(channel_details.outbound_capacity_msat, channel_id=hex::encode(channel_id), + "Channel has enough outbound capacity"); + return Ok(()) + } else { + tracing::debug!(channel_id = hex::encode(channel_id), outbound_capacity_msats = channel_details.outbound_capacity_msat, funding_tx_fees_msats, + "Channel does not have enough outbound capacity to pay jit channel opening fees yet. Waiting."); + tokio::time::sleep(Duration::from_millis(200)).await + } + } + }) + .await?.map_err(|e| anyhow!("{e:#}")) + .with_context(||format!( + "Timed-out waiting for channel {} to become usable", + hex::encode(channel_id) + )) + } } async fn fetch_funding_transaction(txid: Txid) -> Result { diff --git a/mobile/native/src/ln_dlc/mod.rs b/mobile/native/src/ln_dlc/mod.rs index 5c9e9800b..a58b4503b 100644 --- a/mobile/native/src/ln_dlc/mod.rs +++ b/mobile/native/src/ln_dlc/mod.rs @@ -1,6 +1,7 @@ use self::node::WalletHistories; use crate::api; use crate::calculations; +use crate::channel_fee::ChannelFeePaymentSubscriber; use crate::commons::reqwest_client; use crate::config; use crate::event; @@ -246,6 +247,10 @@ pub fn run(data_dir: String, seed_dir: String, runtime: &Runtime) -> Result<()> } }); + event::subscribe(ChannelFeePaymentSubscriber::new( + node.inner.channel_manager.clone(), + )); + NODE.set(node); event::publish(&EventInternal::Init("10101 is ready.".to_string()));