From 75a21b3dd3f78c1d8321fd168e3b1182d3bd4074 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 12 Aug 2024 12:20:54 +0200 Subject: [PATCH] add trampoline client support extract trampoline payment data from label --- libs/sdk-core/src/greenlight/node_api.rs | 76 +++++++++++++++++++++--- libs/sdk-core/src/node_api.rs | 7 +++ libs/sdk-core/src/test_utils.rs | 11 ++++ 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs index 8d7129093..ba47d239e 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -22,7 +22,7 @@ use gl_client::pb::cln::{ }; use gl_client::pb::scheduler::scheduler_client::SchedulerClient; use gl_client::pb::scheduler::{NodeInfoRequest, UpgradeRequest}; -use gl_client::pb::{OffChainPayment, PayStatus}; +use gl_client::pb::{OffChainPayment, PayStatus, TrampolinePayRequest}; use gl_client::scheduler::Scheduler; use gl_client::signer::model::greenlight::{amount, scheduler}; use gl_client::signer::{Error, Signer}; @@ -72,6 +72,14 @@ struct InvoiceLabel { pub payer_amount_msat: Option, } +#[derive(Serialize, Deserialize)] +struct PaymentLabel { + pub unix_nano: u128, + pub trampoline: bool, + pub client_label: Option, + pub amount_msat: u64, +} + impl Greenlight { /// Connects to a live node using the provided seed and config. /// If the node is not registered, it will try to recover it using the seed. @@ -1147,6 +1155,47 @@ impl NodeAPI for Greenlight { payment.try_into() } + async fn send_trampoline_payment( + &self, + bolt11: String, + amount_msat: u64, + label: Option, + trampoline_node_id: Vec, + ) -> NodeResult { + let invoice = parse_invoice(&bolt11)?; + validate_network(invoice.clone(), self.sdk_config.network)?; + let label = serde_json::to_string(&PaymentLabel { + trampoline: true, + client_label: label, + unix_nano: SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos(), + amount_msat, + })?; + let mut client = self.get_client().await?; + let request = TrampolinePayRequest { + bolt11, + trampoline_node_id, + amount_msat, + label, + maxdelay: u32::default(), + description: String::default(), + maxfeepercent: f32::default(), + }; + let result = self + .with_keep_alive(client.trampoline_pay(request)) + .await? + .into_inner(); + + let client = self.get_node_client().await?; + + // Before returning from send_payment we need to make sure it is + // persisted in the backend node. We do so by polling for the payment. + // TODO: Ensure this works with trampoline payments + // NOTE: If this doesn't work with trampoline payments, the sync also + // needs updating. + let payment = Self::fetch_outgoing_payment_with_retry(client, result.payment_hash).await?; + payment.try_into() + } + async fn send_spontaneous_payment( &self, node_id: String, @@ -2114,16 +2163,29 @@ impl TryFrom for Payment { .as_ref() .ok_or(InvoiceError::generic("No bolt11 invoice")) .and_then(|b| parse_invoice(b)); - let payment_amount = payment - .amount_msat - .clone() - .map(|a| a.msat) - .unwrap_or_default(); let payment_amount_sent = payment .amount_sent_msat .clone() .map(|a| a.msat) .unwrap_or_default(); + + // For trampoline payments the amount_msat doesn't match the actual + // amount. If it's a trampoline payment, take the amount from the label. + let (payment_amount, client_label) = serde_json::from_str::(payment.label()) + .ok() + .and_then(|label| { + label + .trampoline + .then_some((label.amount_msat, label.client_label)) + }) + .unwrap_or(( + payment + .amount_msat + .clone() + .map(|a| a.msat) + .unwrap_or_default(), + payment.label.clone(), + )); let status = payment.status().into(); Ok(Payment { @@ -2143,7 +2205,7 @@ impl TryFrom for Payment { details: PaymentDetails::Ln { data: LnPaymentDetails { payment_hash: hex::encode(payment.payment_hash), - label: payment.label.unwrap_or_default(), + label: client_label.unwrap_or_default(), destination_pubkey: payment.destination.map(hex::encode).unwrap_or_default(), payment_preimage: payment.preimage.map(hex::encode).unwrap_or_default(), keysend: payment.bolt11.is_none(), diff --git a/libs/sdk-core/src/node_api.rs b/libs/sdk-core/src/node_api.rs index 5de808263..171a935e1 100644 --- a/libs/sdk-core/src/node_api.rs +++ b/libs/sdk-core/src/node_api.rs @@ -137,6 +137,13 @@ pub trait NodeAPI: Send + Sync { extra_tlvs: Option>, label: Option, ) -> NodeResult; + async fn send_trampoline_payment( + &self, + bolt11: String, + amount_msat: u64, + label: Option, + trampoline_node_id: Vec, + ) -> NodeResult; async fn start(&self) -> NodeResult; /// Attempts to find a payment path "manually" and send the htlcs in a way that will drain diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs index 5d3ae7ab5..626b1ab20 100644 --- a/libs/sdk-core/src/test_utils.rs +++ b/libs/sdk-core/src/test_utils.rs @@ -381,6 +381,17 @@ impl NodeAPI for MockNodeAPI { Ok(payment) } + async fn send_trampoline_payment( + &self, + bolt11: String, + _amount_msat: u64, + _label: Option, + _trampoline_id: Vec, + ) -> NodeResult { + let payment = self.add_dummy_payment_for(bolt11, None, None).await?; + Ok(payment) + } + async fn send_spontaneous_payment( &self, _node_id: String,