From 5fe90d19ab021639442b93a5ab6c55a7ef25adb8 Mon Sep 17 00:00:00 2001 From: Ian Slane Date: Thu, 22 Aug 2024 10:42:51 -0600 Subject: [PATCH 1/5] Introduce `SendingParameters` struct `SendingParameters` allows users to override opinionated values while routing a payment such as `max_total_routing_fees`, `max_path_count` `max_total_cltv_delta`, and `max_channel_saturation_power_of_half` Updated docs for `max_channel_saturation_power_of_half` for clarity. --- bindings/ldk_node.udl | 7 +++++++ src/payment/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ src/uniffi_types.rs | 2 +- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index ec183e78f..420480fde 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -319,6 +319,13 @@ dictionary PaymentDetails { u64 latest_update_timestamp; }; +dictionary SendingParameters { + u64? max_total_routing_fee_msat; + u32? max_total_cltv_expiry_delta; + u8? max_path_count; + u8? max_channel_saturation_power_of_half; +}; + [NonExhaustive] enum Network { "Bitcoin", diff --git a/src/payment/mod.rs b/src/payment/mod.rs index ac4fc5663..d1b12de99 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -13,3 +13,52 @@ pub use onchain::OnchainPayment; pub use spontaneous::SpontaneousPayment; pub use store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; pub use unified_qr::{QrPaymentResult, UnifiedQrPayment}; + +/// Represents information used to route a payment. +#[derive(Clone, Debug, PartialEq)] +pub struct SendingParameters { + /// 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, + + /// 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, + + /// The maximum number of paths that may be used by (MPP) payments. + /// + /// Defaults to [`DEFAULT_MAX_PATH_COUNT`]. + /// + /// [`DEFAULT_MAX_PATH_COUNT`]: lightning::routing::router::DEFAULT_MAX_PATH_COUNT + pub max_path_count: Option, + + /// Selects the maximum share of a channel's total capacity which will be sent over a channel, + /// as a power of 1/2. + /// + /// A higher value prefers to send the payment using more MPP parts whereas + /// a lower value prefers to send larger MPP parts, potentially saturating channels and + /// increasing failure probability for those paths. + /// + /// Note that this restriction will be relaxed during pathfinding after paths which meet this + /// restriction have been found. While paths which meet this criteria will be searched for, it + /// is ultimately up to the scorer to select them over other paths. + /// + /// Examples: + /// + /// | Value | Max Proportion of Channel Capacity Used | + /// |-------|-----------------------------------------| + /// | 0 | Up to 100% of the channel’s capacity | + /// | 1 | Up to 50% of the channel’s capacity | + /// | 2 | Up to 25% of the channel’s capacity | + /// | 3 | Up to 12.5% of the channel’s capacity | + /// + /// Default value: 2 + pub max_channel_saturation_power_of_half: Option, +} diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 7c2142091..22546e03c 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -5,7 +5,7 @@ pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus}; -pub use crate::payment::QrPaymentResult; +pub use crate::payment::{QrPaymentResult, SendingParameters}; pub use lightning::events::{ClosureReason, PaymentFailureReason}; pub use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; From bf4ddff3c2f73a261dab69acf7598f65879dfd4b Mon Sep 17 00:00:00 2001 From: Ian Slane Date: Mon, 12 Aug 2024 10:26:52 -0600 Subject: [PATCH 2/5] Add default `SendingParameters` to node config Introduced `sending_parameters_config` to `Config` for node-wide routing and pathfinding configuration. Also, added default values for `SendingParameters` to ensure reasonable defaults when no custom settings are provided by the user. --- bindings/ldk_node.udl | 1 + src/config.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 420480fde..b8d4161eb 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -16,6 +16,7 @@ dictionary Config { u64 probing_liquidity_limit_multiplier; LogLevel log_level; AnchorChannelsConfig? anchor_channels_config; + SendingParameters? sending_parameters_config; }; dictionary AnchorChannelsConfig { diff --git a/src/config.rs b/src/config.rs index d0e72080f..d3e9fb2af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,7 @@ use std::time::Duration; +use crate::payment::SendingParameters; + use lightning::ln::msgs::SocketAddress; use lightning::util::config::UserConfig; use lightning::util::logger::Level as LogLevel; @@ -86,6 +88,7 @@ pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64; /// | `probing_liquidity_limit_multiplier` | 3 | /// | `log_level` | Debug | /// | `anchor_channels_config` | Some(..) | +/// | `sending_parameters_config` | None | /// /// See [`AnchorChannelsConfig`] for more information on its respective default values. /// @@ -147,6 +150,12 @@ pub struct Config { /// closure. We *will* however still try to get the Anchor spending transactions confirmed /// on-chain with the funds available. pub anchor_channels_config: Option, + + /// Configuration options for payment routing and pathfinding. + /// + /// Setting the `SendingParameters` provides flexibility to customize how payments are routed, + /// including setting limits on routing fees, CLTV expiry, and channel utilization. + pub sending_parameters_config: Option, } impl Default for Config { @@ -164,6 +173,7 @@ impl Default for Config { probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, log_level: DEFAULT_LOG_LEVEL, anchor_channels_config: Some(AnchorChannelsConfig::default()), + sending_parameters_config: None, } } } From d6e6ff7fde4902d70f779c3756fb520c27e87bcd Mon Sep 17 00:00:00 2001 From: Ian Slane Date: Thu, 1 Aug 2024 12:58:26 -0600 Subject: [PATCH 3/5] Add `SendingParameters` to bolt11 `send` Updated `Bolt11Payment` `send` method to accept `SendingParameters`, as a parameter. If the user provided sending params the default values are overridden. --- .../lightningdevkit/ldknode/LibraryTest.kt | 2 +- bindings/ldk_node.udl | 2 +- src/lib.rs | 2 +- src/payment/bolt11.rs | 46 ++++++++++++++++++- src/payment/unified_qr.rs | 2 +- tests/common/mod.rs | 12 ++--- tests/integration_tests_cln.rs | 2 +- tests/integration_tests_rust.rs | 11 ++++- 8 files changed, 64 insertions(+), 15 deletions(-) diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index 6f863e637..b629793cd 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -224,7 +224,7 @@ class LibraryTest { val invoice = node2.bolt11Payment().receive(2500000u, "asdf", 9217u) - node1.bolt11Payment().send(invoice) + node1.bolt11Payment().send(invoice, null) val paymentSuccessfulEvent = node1.waitNextEvent() println("Got event: $paymentSuccessfulEvent") diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index b8d4161eb..a53c3fd54 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -94,7 +94,7 @@ interface Node { interface Bolt11Payment { [Throws=NodeError] - PaymentId send([ByRef]Bolt11Invoice invoice); + PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters); [Throws=NodeError] PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); [Throws=NodeError] diff --git a/src/lib.rs b/src/lib.rs index eb1f38d2e..a1d88ef4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! node.event_handled(); //! //! let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap(); -//! node.bolt11_payment().send(&invoice).unwrap(); +//! node.bolt11_payment().send(&invoice, None).unwrap(); //! //! node.stop().unwrap(); //! } diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 3641d6870..dae77fb6d 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -11,6 +11,7 @@ use crate::payment::store::{ LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, }; +use crate::payment::SendingParameters; use crate::peer_store::{PeerInfo, PeerStore}; use crate::types::{ChannelManager, KeysManager}; @@ -69,13 +70,20 @@ impl Bolt11Payment { } /// Send a payment given an invoice. - pub fn send(&self, invoice: &Bolt11Invoice) -> Result { + /// + /// If [`SendingParameters`] are provided they will override the node's default routing parameters + /// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding + /// default value. Fields that are not set fall back to the node's configured defaults. If no + /// `SendingParameters` are provided, the method fully relies on these defaults. + pub fn send( + &self, invoice: &Bolt11Invoice, sending_parameters: Option, + ) -> Result { 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 })?; @@ -90,6 +98,40 @@ impl Bolt11Payment { } } + if let Some(user_set_params) = sending_parameters { + if let Some(mut default_params) = + self.config.sending_parameters_config.as_ref().cloned() + { + default_params.max_total_routing_fee_msat = user_set_params + .max_total_routing_fee_msat + .or(default_params.max_total_routing_fee_msat); + default_params.max_total_cltv_expiry_delta = user_set_params + .max_total_cltv_expiry_delta + .or(default_params.max_total_cltv_expiry_delta); + default_params.max_path_count = + user_set_params.max_path_count.or(default_params.max_path_count); + default_params.max_channel_saturation_power_of_half = user_set_params + .max_channel_saturation_power_of_half + .or(default_params.max_channel_saturation_power_of_half); + + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + } else if let Some(default_params) = &self.config.sending_parameters_config { + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + let payment_secret = Some(*invoice.payment_secret()); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index b40be5521..b93610115 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -143,7 +143,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, None) { 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), } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 5959bd58e..58a21619b 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -493,8 +493,8 @@ pub(crate) fn do_channel_full_cycle( 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); @@ -526,7 +526,7 @@ pub(crate) fn do_channel_full_cycle( 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)); @@ -579,7 +579,7 @@ pub(crate) fn do_channel_full_cycle( 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 @@ -616,7 +616,7 @@ pub(crate) fn do_channel_full_cycle( .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, @@ -654,7 +654,7 @@ pub(crate) fn do_channel_full_cycle( .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, diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index 7aea13620..95d8f1136 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -98,7 +98,7 @@ fn test_cln() { cln_client.invoice(Some(10_000_000), &rand_label, &rand_label, None, None, None).unwrap(); let parsed_invoice = Bolt11Invoice::from_str(&cln_invoice.bolt11).unwrap(); - node.bolt11_payment().send(&parsed_invoice).unwrap(); + node.bolt11_payment().send(&parsed_invoice, None).unwrap(); common::expect_event!(node, PaymentSuccessful); let cln_listed_invoices = cln_client.listinvoices(Some(&rand_label), None, None, None).unwrap().invoices; diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index ec2b3d917..67de1c9da 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -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, QrPaymentResult, SendingParameters}; use ldk_node::{Builder, Event, NodeError}; use lightning::ln::channelmanager::PaymentId; @@ -156,8 +156,15 @@ fn multi_hop_sending() { // Sleep a bit for gossip to propagate. std::thread::sleep(std::time::Duration::from_secs(1)); + let sending_params = SendingParameters { + max_total_routing_fee_msat: Some(75_000), + max_total_cltv_expiry_delta: Some(1000), + max_path_count: Some(10), + max_channel_saturation_power_of_half: Some(2), + }; + 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(sending_params)).unwrap(); let payment_id = expect_payment_received_event!(&nodes[4], 2_500_000); let fee_paid_msat = Some(2000); From 2b85ba9e33de6f134bfad1d2f566835a205b04f1 Mon Sep 17 00:00:00 2001 From: Ian Slane Date: Thu, 1 Aug 2024 13:32:33 -0600 Subject: [PATCH 4/5] Add `SendingParameters` to bolt11 `send_using_amount` Added the optional `SendingParameters` to `send_using_amount` in `Bolt11Payment`. If the user provides sending params the values will be overridden otherwise they'll use the default values. --- bindings/ldk_node.udl | 2 +- src/payment/bolt11.rs | 44 +++++++++++++++++++++++++++++++++++++++++-- tests/common/mod.rs | 6 +++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index a53c3fd54..1e968921d 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -96,7 +96,7 @@ interface Bolt11Payment { [Throws=NodeError] PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters); [Throws=NodeError] - PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); + PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, SendingParameters? sending_parameters); [Throws=NodeError] void send_probes([ByRef]Bolt11Invoice invoice); [Throws=NodeError] diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index dae77fb6d..e15273232 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -190,14 +190,20 @@ impl Bolt11Payment { } } - /// Send a payment given an invoice and an amount in millisatoshi. + /// Send a payment given an invoice and an amount in millisatoshis. /// /// This will fail if the amount given is less than the value required by the given invoice. /// /// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the /// amount paid to be determined by the user. + /// + /// If [`SendingParameters`] are provided they will override the node's default routing parameters + /// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding + /// default value. Fields that are not set fall back to the node's configured defaults. If no + /// `SendingParameters` are provided, the method fully relies on these defaults. pub fn send_using_amount( &self, invoice: &Bolt11Invoice, amount_msat: u64, + sending_parameters: Option, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -238,9 +244,43 @@ impl Bolt11Payment { .with_bolt11_features(features.clone()) .map_err(|_| Error::InvalidInvoice)?; } - let route_params = + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); + if let Some(user_set_params) = sending_parameters { + if let Some(mut default_params) = + self.config.sending_parameters_config.as_ref().cloned() + { + default_params.max_total_routing_fee_msat = user_set_params + .max_total_routing_fee_msat + .or(default_params.max_total_routing_fee_msat); + default_params.max_total_cltv_expiry_delta = user_set_params + .max_total_cltv_expiry_delta + .or(default_params.max_total_cltv_expiry_delta); + default_params.max_path_count = + user_set_params.max_path_count.or(default_params.max_path_count); + default_params.max_channel_saturation_power_of_half = user_set_params + .max_channel_saturation_power_of_half + .or(default_params.max_channel_saturation_power_of_half); + + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + } else if let Some(default_params) = &self.config.sending_parameters_config { + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); let recipient_fields = RecipientOnionFields::secret_only(*payment_secret); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 58a21619b..b026a233a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -541,7 +541,7 @@ pub(crate) fn do_channel_full_cycle( let underpaid_amount = invoice_amount_2_msat - 1; assert_eq!( Err(NodeError::InvalidAmount), - node_a.bolt11_payment().send_using_amount(&invoice, underpaid_amount) + node_a.bolt11_payment().send_using_amount(&invoice, underpaid_amount, None) ); println!("\nB overpaid receive"); @@ -550,7 +550,7 @@ pub(crate) fn do_channel_full_cycle( println!("\nA overpaid send"); let payment_id = - node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat).unwrap(); + node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat, None).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_amount = match node_b.wait_next_event() { ref e @ Event::PaymentReceived { amount_msat, .. } => { @@ -584,7 +584,7 @@ pub(crate) fn do_channel_full_cycle( println!("\nA send_using_amount"); let payment_id = node_a .bolt11_payment() - .send_using_amount(&variable_amount_invoice, determined_amount_msat) + .send_using_amount(&variable_amount_invoice, determined_amount_msat, None) .unwrap(); expect_event!(node_a, PaymentSuccessful); From 48223362249e7ce68dea2b2293c7985d78c17848 Mon Sep 17 00:00:00 2001 From: Ian Slane Date: Thu, 1 Aug 2024 14:54:51 -0600 Subject: [PATCH 5/5] Add `SendingParameters` to spontaneous `send` Added optional SendingParameters to the send method in SpontaneousPayment. If the user provides sending params the values will be overridden otherwise they remain the same. --- bindings/ldk_node.udl | 2 +- src/payment/spontaneous.rs | 49 +++++++++++++++++++++++++++++++++++--- tests/common/mod.rs | 2 +- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 1e968921d..6e248e3ff 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -136,7 +136,7 @@ interface Bolt12Payment { interface SpontaneousPayment { [Throws=NodeError] - PaymentId send(u64 amount_msat, PublicKey node_id); + PaymentId send(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters); [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); }; diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 13047eab9..a259b685c 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -6,6 +6,7 @@ use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; use crate::payment::store::{ PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, }; +use crate::payment::SendingParameters; use crate::types::{ChannelManager, KeysManager}; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure}; @@ -41,8 +42,15 @@ impl SpontaneousPayment { Self { runtime, channel_manager, keys_manager, payment_store, config, logger } } - /// Send a spontaneous, aka. "keysend", payment - pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result { + /// Send a spontaneous aka. "keysend", payment. + /// + /// If [`SendingParameters`] are provided they will override the node's default routing parameters + /// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding + /// default value. Fields that are not set fall back to the node's configured defaults. If no + /// `SendingParameters` are provided, the method fully relies on these defaults. + pub fn send( + &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, + ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -61,10 +69,45 @@ impl SpontaneousPayment { } } - let route_params = RouteParameters::from_payment_params_and_value( + let mut route_params = RouteParameters::from_payment_params_and_value( PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta), amount_msat, ); + + if let Some(user_set_params) = sending_parameters { + if let Some(mut default_params) = + self.config.sending_parameters_config.as_ref().cloned() + { + default_params.max_total_routing_fee_msat = user_set_params + .max_total_routing_fee_msat + .or(default_params.max_total_routing_fee_msat); + default_params.max_total_cltv_expiry_delta = user_set_params + .max_total_cltv_expiry_delta + .or(default_params.max_total_cltv_expiry_delta); + default_params.max_path_count = + user_set_params.max_path_count.or(default_params.max_path_count); + default_params.max_channel_saturation_power_of_half = user_set_params + .max_channel_saturation_power_of_half + .or(default_params.max_channel_saturation_power_of_half); + + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + } else if let Some(default_params) = &self.config.sending_parameters_config { + route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = + default_params.max_total_cltv_expiry_delta.unwrap_or_default(); + route_params.payment_params.max_path_count = + default_params.max_path_count.unwrap_or_default(); + route_params.payment_params.max_channel_saturation_power_of_half = + default_params.max_channel_saturation_power_of_half.unwrap_or_default(); + } + let recipient_fields = RecipientOnionFields::spontaneous_empty(); match self.channel_manager.send_spontaneous_payment_with_retry( diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b026a233a..6c4dbc1d1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -695,7 +695,7 @@ pub(crate) fn do_channel_full_cycle( println!("\nA send_spontaneous_payment"); let keysend_amount_msat = 2500_000; let keysend_payment_id = - node_a.spontaneous_payment().send(keysend_amount_msat, node_b.node_id()).unwrap(); + node_a.spontaneous_payment().send(keysend_amount_msat, node_b.node_id(), None).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_keysend_amount = match node_b.wait_next_event() { ref e @ Event::PaymentReceived { amount_msat, .. } => {