Skip to content

Commit

Permalink
Add PaymentParameters to bolt11 send
Browse files Browse the repository at this point in the history
Updated Bolt11Payment's send method to accept PaymentParameters,
if the user provided payment params the default values are overridden.
Added the changes in the unifiedqr send method, and modified
the integration tests to include a usage example. Lastly, updated
the ldk_node.udl file for bindings support.
  • Loading branch information
slanesuke committed Aug 1, 2024
1 parent 25d1acc commit c62189b
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 28 deletions.
10 changes: 5 additions & 5 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ interface Node {

interface Bolt11Payment {
[Throws=NodeError]
PaymentId send([ByRef]Bolt11Invoice invoice);
PaymentId send([ByRef]Bolt11Invoice invoice, PaymentParameters? payment_parameters);
[Throws=NodeError]
PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat);
[Throws=NodeError]
Expand Down Expand Up @@ -153,7 +153,7 @@ interface UnifiedQrPayment {
[Throws=NodeError]
string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec);
[Throws=NodeError]
QrPaymentResult send([ByRef]string uri_str);
QrPaymentResult send([ByRef]string uri_str, PaymentParameters? payment_parameters);
};

[Error]
Expand Down Expand Up @@ -318,11 +318,11 @@ dictionary PaymentDetails {
u64 latest_update_timestamp;
};

enum PaymentParameters {
dictionary PaymentParameters {
u64? expiry_time;
u64? max_total_routing_fee_msat;
u64? max_total_cltv_exiry_delta;
}
u32? max_total_cltv_expiry_delta;
};

[NonExhaustive]
enum Network {
Expand Down
28 changes: 22 additions & 6 deletions src/payment/bolt11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ use crate::liquidity::LiquiditySource;
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
use crate::payment::store::{
LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind,
PaymentStatus, PaymentStore,
PaymentParameters, PaymentStatus, PaymentStore,
};
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{ChannelManager, KeysManager};

use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure};
use lightning::ln::{PaymentHash, PaymentPreimage};
use lightning::routing::router::{PaymentParameters, RouteParameters};
use lightning::routing::router::{PaymentParameters as PaymentParams, RouteParameters};

use lightning_invoice::{payment, Bolt11Invoice, Currency};

Expand Down Expand Up @@ -68,14 +68,18 @@ impl Bolt11Payment {
}
}

/// Send a payment given an invoice.
pub fn send(&self, invoice: &Bolt11Invoice) -> Result<PaymentId, Error> {
/// Send a payment given an invoice and optional [`PaymentParameters`].
///
/// [`PaymentParameters`]: PaymentParameters
pub fn send(
&self, invoice: &Bolt11Invoice, payment_parameters: Option<PaymentParameters>,
) -> Result<PaymentId, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let (payment_hash, recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| {
let (payment_hash, recipient_onion, mut route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| {
log_error!(self.logger, "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_using_amount instead.");
Error::InvalidInvoice
})?;
Expand All @@ -90,6 +94,18 @@ impl Bolt11Payment {
}
}

if let Some(payment_params) = payment_parameters {
payment_params
.expiry_time
.map(|expiry| route_params.payment_params.expiry_time = Some(expiry));
payment_params
.max_total_routing_fee_msat
.map(|fee| route_params.max_total_routing_fee_msat = Some(fee));
payment_params
.max_total_cltv_expiry_delta
.map(|delta| route_params.payment_params.max_total_cltv_expiry_delta = delta);
}

let payment_secret = Some(*invoice.payment_secret());
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);

Expand Down Expand Up @@ -184,7 +200,7 @@ impl Bolt11Payment {

let payment_secret = invoice.payment_secret();
let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time());
let mut payment_params = PaymentParameters::from_node_id(
let mut payment_params = PaymentParams::from_node_id(
invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
Expand Down
4 changes: 3 additions & 1 deletion src/payment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ pub use bolt11::Bolt11Payment;
pub use bolt12::Bolt12Payment;
pub use onchain::OnchainPayment;
pub use spontaneous::SpontaneousPayment;
pub use store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
pub use store::{
LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentParameters, PaymentStatus,
};
pub use unified_qr::{QrPaymentResult, UnifiedQrPayment};
6 changes: 5 additions & 1 deletion src/payment/store.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::hex_utils;
use crate::io::{
PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
};
use crate::logger::{log_error, Logger};
use crate::types::DynStore;
use crate::Error;
use crate::hex_utils;

use lightning::ln::channelmanager::PaymentId;
use lightning::ln::msgs::DecodeError;
Expand Down Expand Up @@ -136,16 +136,20 @@ impl Readable for PaymentDetails {
pub struct PaymentParameters {
/// Expiration of a payment to the payee, in seconds relative to the UNIX epoch.
pub expiry_time: Option<u64>,

/// The maximum total fees, in millisatoshi, that may accrue during route finding.
///
/// This limit also applies to the total fees that may arise while retrying failed payment
/// paths.
///
/// Note that values below a few sats may result in some paths being spuriously ignored.
pub max_total_routing_fee_msat: Option<u64>,

/// The maximum total CLTV delta we accept for the route.
///
/// Defaults to [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`].
///
/// [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`]: lightning::routing::router::DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA
pub max_total_cltv_expiry_delta: Option<u32>,
}

Expand Down
10 changes: 7 additions & 3 deletions src/payment/unified_qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
use crate::error::Error;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment::store::PaymentParameters;
use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment};
use crate::Config;

Expand Down Expand Up @@ -118,7 +119,7 @@ impl UnifiedQrPayment {
Ok(format_uri(uri))
}

/// Sends a payment given a [BIP 21] URI.
/// Sends a payment given a [BIP 21] URI and optional [`PaymentParameters`].
///
/// This method parses the provided URI string and attempts to send the payment. If the URI
/// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
Expand All @@ -128,7 +129,10 @@ impl UnifiedQrPayment {
/// occurs, an `Error` is returned detailing the issue encountered.
///
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
pub fn send(&self, uri_str: &str) -> Result<QrPaymentResult, Error> {
/// [`PaymentParameters]: payment::store::PaymentParameters
pub fn send(
&self, uri_str: &str, payment_params: Option<PaymentParameters>,
) -> Result<QrPaymentResult, Error> {
let uri: bip21::Uri<NetworkUnchecked, Extras> =
uri_str.parse().map_err(|_| Error::InvalidUri)?;

Expand All @@ -143,7 +147,7 @@ impl UnifiedQrPayment {
}

if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
match self.bolt11_invoice.send(&invoice) {
match self.bolt11_invoice.send(&invoice, payment_params) {
Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e),
}
Expand Down
4 changes: 3 additions & 1 deletion src/uniffi_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
// Make sure to add any re-exported items that need to be used in uniffi below.

pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus};
pub use crate::payment::store::{
LSPFeeLimits, PaymentDirection, PaymentKind, PaymentParameters, PaymentStatus,
};
pub use crate::payment::QrPaymentResult;

pub use lightning::events::{ClosureReason, PaymentFailureReason};
Expand Down
12 changes: 6 additions & 6 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,8 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
let invoice = node_b.bolt11_payment().receive(invoice_amount_1_msat, &"asdf", 9217).unwrap();

println!("\nA send");
let payment_id = node_a.bolt11_payment().send(&invoice).unwrap();
assert_eq!(node_a.bolt11_payment().send(&invoice), Err(NodeError::DuplicatePayment));
let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap();
assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::DuplicatePayment));

assert_eq!(node_a.list_payments().first().unwrap().id, payment_id);

Expand Down Expand Up @@ -526,7 +526,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. }));

// Assert we fail duplicate outbound payments and check the status hasn't changed.
assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice));
assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice, None));
assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded);
assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound);
assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat));
Expand Down Expand Up @@ -579,7 +579,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
let determined_amount_msat = 2345_678;
assert_eq!(
Err(NodeError::InvalidInvoice),
node_a.bolt11_payment().send(&variable_amount_invoice)
node_a.bolt11_payment().send(&variable_amount_invoice, None)
);
println!("\nA send_using_amount");
let payment_id = node_a
Expand Down Expand Up @@ -616,7 +616,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
.bolt11_payment()
.receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_payment_hash)
.unwrap();
let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice).unwrap();
let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap();

let claimable_amount_msat = expect_payment_claimable_event!(
node_b,
Expand Down Expand Up @@ -654,7 +654,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
.bolt11_payment()
.receive_for_hash(invoice_amount_3_msat, &"asdf", 9217, manual_fail_payment_hash)
.unwrap();
let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice).unwrap();
let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice, None).unwrap();

expect_payment_claimable_event!(
node_b,
Expand Down
16 changes: 11 additions & 5 deletions tests/integration_tests_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use common::{
setup_node, setup_two_nodes, wait_for_tx, TestSyncStore,
};

use ldk_node::payment::{PaymentKind, QrPaymentResult};
use ldk_node::payment::{PaymentKind, PaymentParameters, QrPaymentResult};
use ldk_node::{Builder, Event, NodeError};

use lightning::ln::channelmanager::PaymentId;
Expand Down Expand Up @@ -156,8 +156,14 @@ fn multi_hop_sending() {
// Sleep a bit for gossip to propagate.
std::thread::sleep(std::time::Duration::from_secs(1));

let payment_params = PaymentParameters {
expiry_time: None,
max_total_routing_fee_msat: Some(75_000),
max_total_cltv_expiry_delta: Some(1000),
};

let invoice = nodes[4].bolt11_payment().receive(2_500_000, &"asdf", 9217).unwrap();
nodes[0].bolt11_payment().send(&invoice).unwrap();
nodes[0].bolt11_payment().send(&invoice, Some(payment_params)).unwrap();

let payment_id = expect_payment_received_event!(&nodes[4], 2_500_000);
let fee_paid_msat = Some(2000);
Expand Down Expand Up @@ -630,7 +636,7 @@ fn unified_qr_send_receive() {

let uqr_payment = node_b.unified_qr_payment().receive(expected_amount_sats, "asdf", expiry_sec);
let uri_str = uqr_payment.clone().unwrap();
let offer_payment_id: PaymentId = match node_a.unified_qr_payment().send(&uri_str) {
let offer_payment_id: PaymentId = match node_a.unified_qr_payment().send(&uri_str, None) {
Ok(QrPaymentResult::Bolt12 { payment_id }) => {
println!("\nBolt12 payment sent successfully with PaymentID: {:?}", payment_id);
payment_id
Expand All @@ -652,7 +658,7 @@ fn unified_qr_send_receive() {
// Still needs work
let uri_str_with_invalid_offer = &uri_str[..uri_str.len() - 1];
let invoice_payment_id: PaymentId =
match node_a.unified_qr_payment().send(uri_str_with_invalid_offer) {
match node_a.unified_qr_payment().send(uri_str_with_invalid_offer, None) {
Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => {
panic!("Expected Bolt11 payment but got Bolt12");
},
Expand All @@ -676,7 +682,7 @@ fn unified_qr_send_receive() {
// Removed a character from the offer, so it would move on to the other parameters.
let txid = match node_a
.unified_qr_payment()
.send(&onchain_uqr_payment.as_str()[..onchain_uqr_payment.len() - 1])
.send(&onchain_uqr_payment.as_str()[..onchain_uqr_payment.len() - 1], None)
{
Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => {
panic!("Expected on-chain payment but got Bolt12")
Expand Down

0 comments on commit c62189b

Please sign in to comment.