Skip to content

Commit

Permalink
feat: Add channel fee payment subscriber
Browse files Browse the repository at this point in the history
  • Loading branch information
holzeis committed Jul 3, 2023
1 parent 07033d4 commit c31f6b3
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Charge funding transaction on-chain fees upon receiving and inbound JIT Channel

## [1.0.21] - 2023-07-02

- Fix issue where `Next` button on the create invoice screen was hidden behind keyboard. The keyboard can now be closed by tapping outside the text-field.
Expand Down
3 changes: 2 additions & 1 deletion crates/tests-e2e/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ async fn app_can_be_funded_with_lnd_faucet() -> Result<()> {
assert_eq!(app.rx.wallet_info().unwrap().balances.lightning, 0);

let funding_amount = 50_000;
let funding_transaction_fees = 153;
fund_app_with_faucet(&client, funding_amount).await?;

assert_eq!(app.rx.wallet_info().unwrap().balances.on_chain, 0);
assert_eq!(
app.rx.wallet_info().unwrap().balances.lightning,
funding_amount
funding_amount - funding_transaction_fees
);
Ok(())
}
4 changes: 1 addition & 3 deletions mobile/native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ crate-type = ["rlib", "cdylib", "staticlib"]
anyhow = "1"
base64 = "0.21.0"
bdk = { version = "0.27.0", default-features = false, features = ["key-value-db", "use-esplora-blocking"] }
bitcoin = "0.29"
coordinator-commons = { path = "../../crates/coordinator-commons" }
diesel = { version = "2.0.0", features = ["sqlite", "r2d2", "extras"] }
diesel_migrations = "2.0.0"
Expand Down Expand Up @@ -39,6 +40,3 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["f
trade = { path = "../../crates/trade" }
url = "2.3.1"
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"] }

[dev-dependencies]
bitcoin = "0.29"
2 changes: 2 additions & 0 deletions mobile/native/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::calculations;
use crate::channel_fee::ChannelFeePaymentSubscriber;
use crate::commons::api::Price;
use crate::config;
use crate::config::api::Config;
Expand Down Expand Up @@ -196,6 +197,7 @@ pub fn run(config: Config, app_dir: String, seed_dir: String) -> Result<()> {
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());
orderbook::subscribe(ln_dlc::get_node_key(), runtime)
}

Expand Down
157 changes: 157 additions & 0 deletions mobile/native/src/channel_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use crate::commons::reqwest_client;
use crate::config;
use crate::event::subscriber::Subscriber;
use crate::event::EventInternal;
use crate::event::EventType;
use crate::ln_dlc;
use anyhow::anyhow;
use anyhow::Context;
use anyhow::Result;
use bitcoin::Txid;
use lightning_invoice::Invoice;
use ln_dlc_node::node::rust_dlc_manager::ChannelId;
use serde::Deserialize;
use serde::Serialize;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::Mutex;
use tokio::runtime::Handle;

#[derive(Clone)]
pub struct ChannelFeePaymentSubscriber {
pub open_channel_tx: Arc<Mutex<Option<EsploraTransaction>>>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct EsploraTransaction {
pub txid: String,
pub fee: u32,
}

impl Subscriber for ChannelFeePaymentSubscriber {
fn notify(&self, event: &EventInternal) {
let result = match event {
EventInternal::ChannelReady(channel_id) => {
self.register_funding_transaction(channel_id)
}
EventInternal::PaymentClaimed(amount_msats) => {
self.pay_funding_transaction_fees(*amount_msats)
}
_ => Ok(()),
};

if let Err(e) = result {
tracing::error!("{e:#}");
}
}

fn events(&self) -> Vec<EventType> {
vec![EventType::ChannelReady, EventType::PaymentClaimed]
}
}

impl ChannelFeePaymentSubscriber {
pub fn new() -> Self {
Self {
open_channel_tx: Arc::new(Mutex::new(None)),
}
}

/// 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,
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;

if funding_tx_fees_msats > amount_msats {
tracing::warn!("Trying to pay fees with an amount smaller than the fees!")
}

let invoice_str = tokio::task::block_in_place(|| {
Handle::current().block_on(fetch_funding_transaction_fee_invoice(transaction.fee))
})?;

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();
tracing::info!("Successfully triggered funding transaction fees payment of {funding_tx_fees_msats} msats to {}", config::get_coordinator_info().pubkey);
}
Err(e) => {
tracing::error!("Failed to pay funding transaction fees of {funding_tx_fees_msats} msats to {}. Error: {e:#}", config::get_coordinator_info().pubkey);
}
};

Ok(())
}

/// Register jit channel opening transaction for fee payment
fn register_funding_transaction(&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}");

let txid = ln_dlc::get_funding_transaction(channel_id)?;

let transaction: EsploraTransaction = tokio::task::block_in_place(|| {
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);
Ok(())
}

fn set_funding_transaction(&self, transaction: EsploraTransaction) {
*self
.open_channel_tx
.lock()
.expect("Mutex to not be poisoned") = Some(transaction);
}

fn unset_funding_transaction(&self) {
*self
.open_channel_tx
.lock()
.expect("Mutex to not be poisoned") = None;
}

fn get_funding_transaction(&self) -> Option<EsploraTransaction> {
self.open_channel_tx
.lock()
.expect("Mutex to not be poisoned")
.clone()
}
}

async fn fetch_funding_transaction(txid: Txid) -> Result<EsploraTransaction> {
reqwest_client()
.get(format!("{}tx/{txid}", config::get_esplora_endpoint()))
.send()
.await?
.json()
.await
.map_err(|e| anyhow!("Failed to fetch transaction: {txid} from esplora. Error: {e:?}"))
}

async fn fetch_funding_transaction_fee_invoice(funding_tx_fee: u32) -> Result<String> {
reqwest_client()
.get(format!(
"http://{}/api/invoice?amount={}",
config::get_http_endpoint(),
funding_tx_fee
))
.send()
.await?
.text()
.await
.map_err(|e| anyhow!("Failed to fetch invoice from coordinator. Error:{e:?}"))
}
1 change: 1 addition & 0 deletions mobile/native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod schema;

pub mod api;
pub mod calculations;
mod channel_fee;
pub mod commons;
pub mod config;
pub mod event;
Expand Down
25 changes: 10 additions & 15 deletions mobile/native/src/ln_dlc/lightning_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,23 @@ use tokio::sync::watch::Receiver;
impl Node {
pub async fn listen_for_lightning_events(&self, mut event_receiver: Receiver<Option<Event>>) {
loop {
let event = match event_receiver.changed().await {
match event_receiver.changed().await {
Ok(()) => {
if let Some(event) = event_receiver.borrow().clone() {
event
} else {
continue;
match event {
Event::ChannelReady { channel_id, .. } => {
event::publish(&EventInternal::ChannelReady(channel_id))
}
Event::PaymentClaimed { amount_msat, .. } => {
event::publish(&EventInternal::PaymentClaimed(amount_msat))
}
_ => tracing::trace!("Ignoring event on the mobile app"),
}
}
}
Err(e) => {
tracing::error!("Failed to receive event: {e:#}");
continue;
}
};

match event {
Event::ChannelReady { channel_id, .. } => {
event::publish(&EventInternal::ChannelReady(channel_id))
}
Event::PaymentClaimed { amount_msat, .. } => {
event::publish(&EventInternal::PaymentClaimed(amount_msat))
}
_ => tracing::debug!("Ignoring event on the mobile app"),
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions mobile/native/src/ln_dlc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ use anyhow::Result;
use bdk::bitcoin::secp256k1::rand::thread_rng;
use bdk::bitcoin::secp256k1::rand::RngCore;
use bdk::bitcoin::secp256k1::SecretKey;
use bdk::bitcoin::Txid;
use bdk::bitcoin::XOnlyPublicKey;
use bdk::BlockTime;
use coordinator_commons::TradeParams;
use itertools::chain;
use itertools::Itertools;
use lightning::util::events::Event;
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::LnDlcNodeSettings;
use ln_dlc_node::node::NodeInfo;
use ln_dlc_node::seed::Bip39Seed;
Expand Down Expand Up @@ -82,6 +85,27 @@ pub fn get_oracle_pubkey() -> XOnlyPublicKey {
NODE.get().inner.oracle_pk()
}

pub fn get_funding_transaction(channel_id: &ChannelId) -> Result<Txid> {
let node = NODE.get();
let channel_details = node.inner.channel_manager.get_channel_details(channel_id);

let funding_transaction = match channel_details {
Some(channel_details) => match channel_details.funding_txo {
Some(funding_txo) => funding_txo.txid,
None => bail!(
"Could not find funding transaction for channel {}",
hex::encode(channel_id)
),
},
None => bail!(
"Could not find channel details for {}",
hex::encode(channel_id)
),
};

Ok(funding_transaction)
}

/// Lazily creates a multi threaded runtime with the the number of worker threads corresponding to
/// the number of available cores.
pub fn get_or_create_tokio_runtime() -> Result<&'static Runtime> {
Expand Down

0 comments on commit c31f6b3

Please sign in to comment.