Skip to content

Commit 398ece5

Browse files
authored
Merge pull request #336 from slanesuke/2024-07-introduce-PaymentParameters
Add `SendingParameters` struct for customizable payments
2 parents e233188 + 4822336 commit 398ece5

File tree

12 files changed

+226
-27
lines changed

12 files changed

+226
-27
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class LibraryTest {
224224

225225
val invoice = node2.bolt11Payment().receive(2500000u, "asdf", 9217u)
226226

227-
node1.bolt11Payment().send(invoice)
227+
node1.bolt11Payment().send(invoice, null)
228228

229229
val paymentSuccessfulEvent = node1.waitNextEvent()
230230
println("Got event: $paymentSuccessfulEvent")

bindings/ldk_node.udl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dictionary Config {
1616
u64 probing_liquidity_limit_multiplier;
1717
LogLevel log_level;
1818
AnchorChannelsConfig? anchor_channels_config;
19+
SendingParameters? sending_parameters_config;
1920
};
2021

2122
dictionary AnchorChannelsConfig {
@@ -93,9 +94,9 @@ interface Node {
9394

9495
interface Bolt11Payment {
9596
[Throws=NodeError]
96-
PaymentId send([ByRef]Bolt11Invoice invoice);
97+
PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters);
9798
[Throws=NodeError]
98-
PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat);
99+
PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, SendingParameters? sending_parameters);
99100
[Throws=NodeError]
100101
void send_probes([ByRef]Bolt11Invoice invoice);
101102
[Throws=NodeError]
@@ -135,7 +136,7 @@ interface Bolt12Payment {
135136

136137
interface SpontaneousPayment {
137138
[Throws=NodeError]
138-
PaymentId send(u64 amount_msat, PublicKey node_id);
139+
PaymentId send(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters);
139140
[Throws=NodeError]
140141
void send_probes(u64 amount_msat, PublicKey node_id);
141142
};
@@ -319,6 +320,13 @@ dictionary PaymentDetails {
319320
u64 latest_update_timestamp;
320321
};
321322

323+
dictionary SendingParameters {
324+
u64? max_total_routing_fee_msat;
325+
u32? max_total_cltv_expiry_delta;
326+
u8? max_path_count;
327+
u8? max_channel_saturation_power_of_half;
328+
};
329+
322330
[NonExhaustive]
323331
enum Network {
324332
"Bitcoin",

src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::time::Duration;
22

3+
use crate::payment::SendingParameters;
4+
35
use lightning::ln::msgs::SocketAddress;
46
use lightning::util::config::UserConfig;
57
use lightning::util::logger::Level as LogLevel;
@@ -86,6 +88,7 @@ pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64;
8688
/// | `probing_liquidity_limit_multiplier` | 3 |
8789
/// | `log_level` | Debug |
8890
/// | `anchor_channels_config` | Some(..) |
91+
/// | `sending_parameters_config` | None |
8992
///
9093
/// See [`AnchorChannelsConfig`] for more information on its respective default values.
9194
///
@@ -147,6 +150,12 @@ pub struct Config {
147150
/// closure. We *will* however still try to get the Anchor spending transactions confirmed
148151
/// on-chain with the funds available.
149152
pub anchor_channels_config: Option<AnchorChannelsConfig>,
153+
154+
/// Configuration options for payment routing and pathfinding.
155+
///
156+
/// Setting the `SendingParameters` provides flexibility to customize how payments are routed,
157+
/// including setting limits on routing fees, CLTV expiry, and channel utilization.
158+
pub sending_parameters_config: Option<SendingParameters>,
150159
}
151160

152161
impl Default for Config {
@@ -164,6 +173,7 @@ impl Default for Config {
164173
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
165174
log_level: DEFAULT_LOG_LEVEL,
166175
anchor_channels_config: Some(AnchorChannelsConfig::default()),
176+
sending_parameters_config: None,
167177
}
168178
}
169179
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
//! node.event_handled();
5757
//!
5858
//! let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap();
59-
//! node.bolt11_payment().send(&invoice).unwrap();
59+
//! node.bolt11_payment().send(&invoice, None).unwrap();
6060
//!
6161
//! node.stop().unwrap();
6262
//! }

src/payment/bolt11.rs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::payment::store::{
1111
LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind,
1212
PaymentStatus, PaymentStore,
1313
};
14+
use crate::payment::SendingParameters;
1415
use crate::peer_store::{PeerInfo, PeerStore};
1516
use crate::types::{ChannelManager, KeysManager};
1617

@@ -69,13 +70,20 @@ impl Bolt11Payment {
6970
}
7071

7172
/// Send a payment given an invoice.
72-
pub fn send(&self, invoice: &Bolt11Invoice) -> Result<PaymentId, Error> {
73+
///
74+
/// If [`SendingParameters`] are provided they will override the node's default routing parameters
75+
/// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding
76+
/// default value. Fields that are not set fall back to the node's configured defaults. If no
77+
/// `SendingParameters` are provided, the method fully relies on these defaults.
78+
pub fn send(
79+
&self, invoice: &Bolt11Invoice, sending_parameters: Option<SendingParameters>,
80+
) -> Result<PaymentId, Error> {
7381
let rt_lock = self.runtime.read().unwrap();
7482
if rt_lock.is_none() {
7583
return Err(Error::NotRunning);
7684
}
7785

78-
let (payment_hash, recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| {
86+
let (payment_hash, recipient_onion, mut route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| {
7987
log_error!(self.logger, "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_using_amount instead.");
8088
Error::InvalidInvoice
8189
})?;
@@ -90,6 +98,40 @@ impl Bolt11Payment {
9098
}
9199
}
92100

101+
if let Some(user_set_params) = sending_parameters {
102+
if let Some(mut default_params) =
103+
self.config.sending_parameters_config.as_ref().cloned()
104+
{
105+
default_params.max_total_routing_fee_msat = user_set_params
106+
.max_total_routing_fee_msat
107+
.or(default_params.max_total_routing_fee_msat);
108+
default_params.max_total_cltv_expiry_delta = user_set_params
109+
.max_total_cltv_expiry_delta
110+
.or(default_params.max_total_cltv_expiry_delta);
111+
default_params.max_path_count =
112+
user_set_params.max_path_count.or(default_params.max_path_count);
113+
default_params.max_channel_saturation_power_of_half = user_set_params
114+
.max_channel_saturation_power_of_half
115+
.or(default_params.max_channel_saturation_power_of_half);
116+
117+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
118+
route_params.payment_params.max_total_cltv_expiry_delta =
119+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
120+
route_params.payment_params.max_path_count =
121+
default_params.max_path_count.unwrap_or_default();
122+
route_params.payment_params.max_channel_saturation_power_of_half =
123+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
124+
}
125+
} else if let Some(default_params) = &self.config.sending_parameters_config {
126+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
127+
route_params.payment_params.max_total_cltv_expiry_delta =
128+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
129+
route_params.payment_params.max_path_count =
130+
default_params.max_path_count.unwrap_or_default();
131+
route_params.payment_params.max_channel_saturation_power_of_half =
132+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
133+
}
134+
93135
let payment_secret = Some(*invoice.payment_secret());
94136
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
95137

@@ -148,14 +190,20 @@ impl Bolt11Payment {
148190
}
149191
}
150192

151-
/// Send a payment given an invoice and an amount in millisatoshi.
193+
/// Send a payment given an invoice and an amount in millisatoshis.
152194
///
153195
/// This will fail if the amount given is less than the value required by the given invoice.
154196
///
155197
/// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the
156198
/// amount paid to be determined by the user.
199+
///
200+
/// If [`SendingParameters`] are provided they will override the node's default routing parameters
201+
/// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding
202+
/// default value. Fields that are not set fall back to the node's configured defaults. If no
203+
/// `SendingParameters` are provided, the method fully relies on these defaults.
157204
pub fn send_using_amount(
158205
&self, invoice: &Bolt11Invoice, amount_msat: u64,
206+
sending_parameters: Option<SendingParameters>,
159207
) -> Result<PaymentId, Error> {
160208
let rt_lock = self.runtime.read().unwrap();
161209
if rt_lock.is_none() {
@@ -196,9 +244,43 @@ impl Bolt11Payment {
196244
.with_bolt11_features(features.clone())
197245
.map_err(|_| Error::InvalidInvoice)?;
198246
}
199-
let route_params =
247+
let mut route_params =
200248
RouteParameters::from_payment_params_and_value(payment_params, amount_msat);
201249

250+
if let Some(user_set_params) = sending_parameters {
251+
if let Some(mut default_params) =
252+
self.config.sending_parameters_config.as_ref().cloned()
253+
{
254+
default_params.max_total_routing_fee_msat = user_set_params
255+
.max_total_routing_fee_msat
256+
.or(default_params.max_total_routing_fee_msat);
257+
default_params.max_total_cltv_expiry_delta = user_set_params
258+
.max_total_cltv_expiry_delta
259+
.or(default_params.max_total_cltv_expiry_delta);
260+
default_params.max_path_count =
261+
user_set_params.max_path_count.or(default_params.max_path_count);
262+
default_params.max_channel_saturation_power_of_half = user_set_params
263+
.max_channel_saturation_power_of_half
264+
.or(default_params.max_channel_saturation_power_of_half);
265+
266+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
267+
route_params.payment_params.max_total_cltv_expiry_delta =
268+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
269+
route_params.payment_params.max_path_count =
270+
default_params.max_path_count.unwrap_or_default();
271+
route_params.payment_params.max_channel_saturation_power_of_half =
272+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
273+
}
274+
} else if let Some(default_params) = &self.config.sending_parameters_config {
275+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
276+
route_params.payment_params.max_total_cltv_expiry_delta =
277+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
278+
route_params.payment_params.max_path_count =
279+
default_params.max_path_count.unwrap_or_default();
280+
route_params.payment_params.max_channel_saturation_power_of_half =
281+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
282+
}
283+
202284
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
203285
let recipient_fields = RecipientOnionFields::secret_only(*payment_secret);
204286

src/payment/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,52 @@ pub use onchain::OnchainPayment;
1313
pub use spontaneous::SpontaneousPayment;
1414
pub use store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
1515
pub use unified_qr::{QrPaymentResult, UnifiedQrPayment};
16+
17+
/// Represents information used to route a payment.
18+
#[derive(Clone, Debug, PartialEq)]
19+
pub struct SendingParameters {
20+
/// The maximum total fees, in millisatoshi, that may accrue during route finding.
21+
///
22+
/// This limit also applies to the total fees that may arise while retrying failed payment
23+
/// paths.
24+
///
25+
/// Note that values below a few sats may result in some paths being spuriously ignored.
26+
pub max_total_routing_fee_msat: Option<u64>,
27+
28+
/// The maximum total CLTV delta we accept for the route.
29+
///
30+
/// Defaults to [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`].
31+
///
32+
/// [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`]: lightning::routing::router::DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA
33+
pub max_total_cltv_expiry_delta: Option<u32>,
34+
35+
/// The maximum number of paths that may be used by (MPP) payments.
36+
///
37+
/// Defaults to [`DEFAULT_MAX_PATH_COUNT`].
38+
///
39+
/// [`DEFAULT_MAX_PATH_COUNT`]: lightning::routing::router::DEFAULT_MAX_PATH_COUNT
40+
pub max_path_count: Option<u8>,
41+
42+
/// Selects the maximum share of a channel's total capacity which will be sent over a channel,
43+
/// as a power of 1/2.
44+
///
45+
/// A higher value prefers to send the payment using more MPP parts whereas
46+
/// a lower value prefers to send larger MPP parts, potentially saturating channels and
47+
/// increasing failure probability for those paths.
48+
///
49+
/// Note that this restriction will be relaxed during pathfinding after paths which meet this
50+
/// restriction have been found. While paths which meet this criteria will be searched for, it
51+
/// is ultimately up to the scorer to select them over other paths.
52+
///
53+
/// Examples:
54+
///
55+
/// | Value | Max Proportion of Channel Capacity Used |
56+
/// |-------|-----------------------------------------|
57+
/// | 0 | Up to 100% of the channel’s capacity |
58+
/// | 1 | Up to 50% of the channel’s capacity |
59+
/// | 2 | Up to 25% of the channel’s capacity |
60+
/// | 3 | Up to 12.5% of the channel’s capacity |
61+
///
62+
/// Default value: 2
63+
pub max_channel_saturation_power_of_half: Option<u8>,
64+
}

src/payment/spontaneous.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
66
use crate::payment::store::{
77
PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore,
88
};
9+
use crate::payment::SendingParameters;
910
use crate::types::{ChannelManager, KeysManager};
1011

1112
use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure};
@@ -41,8 +42,15 @@ impl SpontaneousPayment {
4142
Self { runtime, channel_manager, keys_manager, payment_store, config, logger }
4243
}
4344

44-
/// Send a spontaneous, aka. "keysend", payment
45-
pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result<PaymentId, Error> {
45+
/// Send a spontaneous aka. "keysend", payment.
46+
///
47+
/// If [`SendingParameters`] are provided they will override the node's default routing parameters
48+
/// on a per-field basis. Each field in `SendingParameters` that is set replaces the corresponding
49+
/// default value. Fields that are not set fall back to the node's configured defaults. If no
50+
/// `SendingParameters` are provided, the method fully relies on these defaults.
51+
pub fn send(
52+
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>,
53+
) -> Result<PaymentId, Error> {
4654
let rt_lock = self.runtime.read().unwrap();
4755
if rt_lock.is_none() {
4856
return Err(Error::NotRunning);
@@ -61,10 +69,45 @@ impl SpontaneousPayment {
6169
}
6270
}
6371

64-
let route_params = RouteParameters::from_payment_params_and_value(
72+
let mut route_params = RouteParameters::from_payment_params_and_value(
6573
PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta),
6674
amount_msat,
6775
);
76+
77+
if let Some(user_set_params) = sending_parameters {
78+
if let Some(mut default_params) =
79+
self.config.sending_parameters_config.as_ref().cloned()
80+
{
81+
default_params.max_total_routing_fee_msat = user_set_params
82+
.max_total_routing_fee_msat
83+
.or(default_params.max_total_routing_fee_msat);
84+
default_params.max_total_cltv_expiry_delta = user_set_params
85+
.max_total_cltv_expiry_delta
86+
.or(default_params.max_total_cltv_expiry_delta);
87+
default_params.max_path_count =
88+
user_set_params.max_path_count.or(default_params.max_path_count);
89+
default_params.max_channel_saturation_power_of_half = user_set_params
90+
.max_channel_saturation_power_of_half
91+
.or(default_params.max_channel_saturation_power_of_half);
92+
93+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
94+
route_params.payment_params.max_total_cltv_expiry_delta =
95+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
96+
route_params.payment_params.max_path_count =
97+
default_params.max_path_count.unwrap_or_default();
98+
route_params.payment_params.max_channel_saturation_power_of_half =
99+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
100+
}
101+
} else if let Some(default_params) = &self.config.sending_parameters_config {
102+
route_params.max_total_routing_fee_msat = default_params.max_total_routing_fee_msat;
103+
route_params.payment_params.max_total_cltv_expiry_delta =
104+
default_params.max_total_cltv_expiry_delta.unwrap_or_default();
105+
route_params.payment_params.max_path_count =
106+
default_params.max_path_count.unwrap_or_default();
107+
route_params.payment_params.max_channel_saturation_power_of_half =
108+
default_params.max_channel_saturation_power_of_half.unwrap_or_default();
109+
}
110+
68111
let recipient_fields = RecipientOnionFields::spontaneous_empty();
69112

70113
match self.channel_manager.send_spontaneous_payment_with_retry(

src/payment/unified_qr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl UnifiedQrPayment {
143143
}
144144

145145
if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
146-
match self.bolt11_invoice.send(&invoice) {
146+
match self.bolt11_invoice.send(&invoice, None) {
147147
Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
148148
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),
149149
}

src/uniffi_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
77
pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus};
8-
pub use crate::payment::QrPaymentResult;
8+
pub use crate::payment::{QrPaymentResult, SendingParameters};
99

1010
pub use lightning::events::{ClosureReason, PaymentFailureReason};
1111
pub use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};

0 commit comments

Comments
 (0)