Skip to content

Commit

Permalink
wip: charge fee when opening a jit channel
Browse files Browse the repository at this point in the history
  • Loading branch information
holzeis committed Jun 29, 2023
1 parent 79dfcce commit e9597d5
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 7 deletions.
14 changes: 12 additions & 2 deletions crates/ln-dlc-node/src/ln/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use std::sync::Arc;
use std::sync::MutexGuard;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::sync::watch;

/// The speed at which we want a transaction to confirm used for feerate estimation.
///
Expand All @@ -54,6 +55,7 @@ pub struct EventHandler<S> {
pending_intercepted_htlcs: PendingInterceptedHtlcs,
peer_manager: Arc<PeerManager>,
fee_rate_estimator: Arc<FeeRateEstimator>,
event_sender: Option<watch::Sender<Option<Event>>>,
}

#[allow(clippy::too_many_arguments)]
Expand All @@ -71,6 +73,7 @@ where
pending_intercepted_htlcs: PendingInterceptedHtlcs,
peer_manager: Arc<PeerManager>,
fee_rate_estimator: Arc<FeeRateEstimator>,
event_sender: Option<watch::Sender<Option<Event>>>,
) -> Self {
Self {
channel_manager,
Expand All @@ -82,6 +85,7 @@ where
pending_intercepted_htlcs,
peer_manager,
fee_rate_estimator,
event_sender,
}
}

Expand All @@ -91,10 +95,17 @@ where

let event_str = format!("{event:?}");

match self.match_event(event).await {
match self.match_event(event.clone()).await {
Ok(()) => tracing::debug!(event = ?event_str, "Successfully handled event"),
Err(e) => tracing::error!("Failed to handle event. Error {e:#}"),
}

if let Some(event_sender) = &self.event_sender {
match event_sender.send(Some(event)) {
Ok(()) => tracing::trace!("Sent event to subscriber"),
Err(e) => tracing::error!("Failed to send event to subscriber: {e:#}"),
}
}
}

async fn match_event(&self, event: Event) -> Result<()> {
Expand Down Expand Up @@ -678,7 +689,6 @@ where
);
}
};

Ok(())
}
}
Expand Down
52 changes: 52 additions & 0 deletions crates/ln-dlc-node/src/node/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,28 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::Network;
use lightning::ln::channelmanager::PaymentId;
use lightning::ln::channelmanager::Retry;
use lightning::ln::channelmanager::MIN_CLTV_EXPIRY_DELTA;
use lightning::ln::PaymentHash;
use lightning::routing::gossip::RoutingFees;
use lightning::routing::router::PaymentParameters;
use lightning::routing::router::RouteHint;
use lightning::routing::router::RouteHintHop;
use lightning::routing::router::RouteParameters;
use lightning_invoice::payment::pay_invoice;
use lightning_invoice::payment::PaymentError;
use lightning_invoice::Currency;
use lightning_invoice::Invoice;
use lightning_invoice::InvoiceBuilder;
use rand::Rng;
use std::time::Duration;
use std::time::SystemTime;
use time::OffsetDateTime;

// The timeout after which we abandon retrying failed payments.
const LDK_PAYMENT_RETRY_TIMEOUT: Duration = Duration::from_secs(10);

impl<P> Node<P>
where
P: Storage,
Expand Down Expand Up @@ -154,6 +161,51 @@ where
}
}

#[autometrics]
pub fn send_spontaneous_payment(
&self,
amount_msat: u64,
receiver: PublicKey,
) -> Result<PaymentHash> {
tracing::info!("Sending spontaneous payment of {amount_msat} to {receiver}");
let mut payment_id = [0u8; 32];
rand::thread_rng().fill(&mut payment_id);

let route_params = RouteParameters {
payment_params: PaymentParameters::for_keysend(receiver, MIN_CLTV_EXPIRY_DELTA.into()),
final_value_msat: amount_msat,
};

let payment_hash = self
.channel_manager
.send_spontaneous_payment_with_retry(
None,
PaymentId(payment_id),
route_params,
Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT),
)
.map_err(|e| {
anyhow!(
"Failed to send spontaneous payment to {}. Error: {e:?}",
receiver
)
})?;

self.storage.insert_payment(
payment_hash,
PaymentInfo {
preimage: None,
secret: None,
status: HTLCStatus::Pending,
amt_msat: MillisatAmount(Some(amount_msat)),
flow: PaymentFlow::Outbound,
timestamp: OffsetDateTime::now_utc(),
},
)?;

Ok(payment_hash)
}

#[autometrics]
pub fn send_payment(&self, invoice: &Invoice) -> Result<()> {
let status = match pay_invoice(invoice, Retry::Attempts(10), &self.channel_manager) {
Expand Down
7 changes: 7 additions & 0 deletions crates/ln-dlc-node/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;
use std::time::SystemTime;
use tokio::sync::watch;
use tokio::sync::RwLock;

mod channel_manager;
Expand All @@ -73,6 +74,7 @@ pub use channel_manager::ChannelManager;
pub use dlc_channel::dlc_message_name;
pub use dlc_channel::sub_channel_message_name;
pub use invoice::HTLCStatus;
use lightning::util::events::Event;
pub use storage::InMemoryStore;
pub use storage::Storage;
pub use sub_channel_manager::SubChannelManager;
Expand Down Expand Up @@ -175,6 +177,7 @@ where
seed: Bip39Seed,
ephemeral_randomness: [u8; 32],
oracle: OracleInfo,
event_sender: watch::Sender<Option<Event>>,
) -> Result<Self> {
let user_config = app_config();
Node::new(
Expand All @@ -194,6 +197,7 @@ where
user_config,
LnDlcNodeSettings::default(),
oracle.into(),
Some(event_sender),
)
}

Expand Down Expand Up @@ -238,6 +242,7 @@ where
user_config,
settings,
oracle.into(),
None,
)
}

Expand All @@ -261,6 +266,7 @@ where
ldk_user_config: UserConfig,
settings: LnDlcNodeSettings,
oracle_client: P2PDOracleClient,
event_sender: Option<watch::Sender<Option<Event>>>,
) -> Result<Self> {
let time_since_unix_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;

Expand Down Expand Up @@ -483,6 +489,7 @@ where
Arc::new(Mutex::new(HashMap::new())),
peer_manager.clone(),
fee_rate_estimator.clone(),
event_sender,
);

// Connection manager
Expand Down
1 change: 1 addition & 0 deletions crates/ln-dlc-node/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ impl Node<InMemoryStore> {
user_config,
LnDlcNodeSettings::default(),
oracle.into(),
None,
)?;

tracing::debug!(%name, info = %node.info, "Node started");
Expand Down
6 changes: 6 additions & 0 deletions crates/tests-e2e/src/test_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ impl Subscriber for Senders {
tracing::trace!(?prices, "Received price update event");
// TODO: Add prices from orderbook_commons
}
native::event::EventInternal::ChannelReady(channel_id) => {
tracing::trace!(?channel_id, "Received channel ready event");
}
native::event::EventInternal::PaymentClaimed(amount_msats) => {
tracing::trace!(amount_msats, "Received payment claimed event");
}
}
}

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::ChannelSubscriber;
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(ChannelSubscriber::new());
orderbook::subscribe(ln_dlc::get_node_key(), runtime)
}

Expand Down
149 changes: 149 additions & 0 deletions mobile/native/src/channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use crate::config;
use crate::event::subscriber::Subscriber;
use crate::event::EventInternal;
use crate::event::EventType;
use crate::ln_dlc;
use ln_dlc_node::node::rust_dlc_manager::ChannelId;
use serde::Deserialize;
use serde::Serialize;
use std::sync::Arc;
use std::sync::Mutex;
use tokio::runtime::Handle;

#[derive(Clone)]
pub struct ChannelSubscriber {
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 ChannelSubscriber {
fn notify(&self, event: &EventInternal) {
match event {
EventInternal::ChannelReady(channel_id) => {
self.fetch_channel_opening_fee(channel_id);
}
EventInternal::PaymentClaimed(amount_msats) => {
self.pay_channel_fees(*amount_msats);
}
_ => {}
}
}

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

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

// attempts to pay channel fee with received payment (amount in msats)
fn pay_channel_fees(&self, amount_msats: u64) {
tracing::debug!("Received payment of {amount_msats}.");
if let Some(transaction) = self.get_funding_transaction() {
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 receiver = config::get_coordinator_info().pubkey;
match ln_dlc::send_spontaneous_payment(funding_tx_fees_msats, receiver) {
Ok(payment_hash) => {
let payment_hash_as_str = hex::encode(payment_hash.0);
// 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} to {receiver}. Payment hash: {payment_hash_as_str}");
}
Err(e) => {
tracing::error!("Failed to pay funding transaction fees of {funding_tx_fees_msats} to {receiver}. Error: {e:#}");
}
}
} else {
tracing::debug!("No pending funding transaction found!");
}
}

// Fetches the channel opening fee from esplora
fn fetch_channel_opening_fee(&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(async move {
tracing::debug!(
"-------------------> Querying {}",
format!("{}/tx/{txid}", config::get_esplora_endpoint())
);

match reqwest::get(format!("{}tx/{txid}", config::get_esplora_endpoint()))
.await
{
Ok(response) => {
// tracing::debug!("---------------> Response: {}",
// response.text().await.unwrap());
match response.json().await {
Ok(response) => Some(response),
Err(e) => {
tracing::error!(
"Failed to fetch transaction from esplora. Error: {e:#}"
);
None
}
}
}
Err(e) => {
tracing::error!(
"Failed to fetch transaction from esplora. Error: {e:#}"
);
None
}
}
})
});

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()
}
}
Loading

0 comments on commit e9597d5

Please sign in to comment.