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 Jun 30, 2023
1 parent 8b3d165 commit c50b05d
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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
- 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.
- Fix panic when processing accept message while peer is disconnected.
- Configurable oracle endpoint and public key
Expand Down
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
169 changes: 169 additions & 0 deletions mobile/native/src/channel_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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::Result;
use ln_dlc_node::node::rust_dlc_manager::ChannelId;
use serde::Deserialize;
use serde::Serialize;
use std::sync::Arc;
use std::sync::Mutex;
use bitcoin::Txid;
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) {
match event {
EventInternal::ChannelReady(channel_id) => {
self.register_funding_transaction(channel_id);
}
EventInternal::PaymentClaimed(amount_msats) => {
self.pay_funding_transaction_fees(*amount_msats);
}
_ => {}
}
}

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) {
let transaction = match self.get_funding_transaction() {
Some(transaction) => transaction,
None => {
tracing::debug!("No pending funding transaction found!");
return;
}
};

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 = tokio::task::block_in_place(|| {
Handle::current().block_on(self.fetch_funding_transaction_fee_invoice(transaction.fee))
})
.ok();

if let Some(invoice) = invoice {
match ln_dlc::send_payment(&invoice) {
Ok(_) => {
// unset the funding transaction marking it as being paid.
self.unset_funding_transaction();
tracing::info!("Successfully paid funding transaction fees 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);
}
}
}
}

// register jit channel opening transaction for fee payment
fn register_funding_transaction(&self, channel_id: &ChannelId) {
let channel_id_as_str = hex::encode(channel_id);
tracing::debug!("Received new inbound channel with id {channel_id_as_str}");

match ln_dlc::get_funding_transaction(channel_id) {
Ok(txid) => {
let transaction: Option<EsploraTransaction> = tokio::task::block_in_place(|| {
Handle::current().block_on(self.fetch_funding_transaction(txid))
})
.ok();

if let Some(transaction) = transaction {
tracing::debug!(
"Successfully fetched transaction fees of {} for new inbound channel with id {channel_id_as_str}",
transaction.fee
);
self.set_funding_transaction(transaction);
}
}
Err(e) => tracing::error!("{e:#}"),
}
}

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(&self, txid: Txid) -> Result<EsploraTransaction> {
let result = reqwest_client()
.get(format!("{}tx/{txid}", config::get_esplora_endpoint()))
.send()
.await?
.json()
.await
.map_err(|e| anyhow!("{e:?}"));

if let Err(e) = &result {
tracing::error!("Failed to fetch transaction from esplora. Error: {e:#}");
}

result
}

async fn fetch_funding_transaction_fee_invoice(&self, funding_tx_fee: u32) -> Result<String> {
let result = reqwest_client()
.get(format!(
"http://{}/api/invoice?amount={}",
config::get_http_endpoint(),
funding_tx_fee
))
.send()
.await?
.text()
.await
.map_err(|e| anyhow!("{e:?}"));

if let Err(e) = &result {
tracing::error!("Failed to fetch invoice from coordinator. Error: {e:#}");
}

result
}
}
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
16 changes: 16 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,19 @@ 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);

if let Some(channel_details) = channel_details {
if let Some(funding_txo) = channel_details.funding_txo {
return Ok(funding_txo.txid);
}
}

bail!("Could not find funding transaction for {:?}", channel_id);
}

/// 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 c50b05d

Please sign in to comment.