Skip to content

Commit d0a914c

Browse files
committed
Allow BOLT 11 payments to be a part of a larger MPP payment
In some uses of LDK we need the ability to send HTLCs for only a portion of some larger MPP payment. This allows payers to make single payments which spend funds from multiple wallets, which may be important for ecash wallets holding funds in multiple mints or graduated wallets which hold funds across a trusted wallet and a self-custodial wallet. In the previous few commits we added support for making these kinds of payments when using the payment methods which explicitly accepted a `RecipientOnionFields`. Here we also add support for such payments made via the `pay_for_bolt11_invoice` method, utilizing the new `OptionalBolt11PaymentParams` to hide the parameter from most calls. Test mostly by Claude
1 parent 811c6e5 commit d0a914c

File tree

4 files changed

+205
-6
lines changed

4 files changed

+205
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,20 @@ pub struct OptionalBolt11PaymentParams {
686686
/// will ultimately fail once all pending paths have failed (generating an
687687
/// [`Event::PaymentFailed`]).
688688
pub retry_strategy: Retry,
689+
/// If the payment being made from this node is part of a larger MPP payment from multiple
690+
/// nodes (i.e. because a single payment is being made from multiple wallets), you can specify
691+
/// the total amount being paid here.
692+
///
693+
/// If this is set, it must be at least the [`Bolt11Invoice::amount_milli_satoshis`] for the
694+
/// invoice provided to [`ChannelManager::pay_for_bolt11_invoice`]. Further, if this is set,
695+
/// the `amount_msats` provided to [`ChannelManager::pay_for_bolt11_invoice`] is allowed to be
696+
/// lower than [`Bolt11Invoice::amount_milli_satoshis`] (as the payment we're making may be a
697+
/// small part of the amount needed to meet the invoice's minimum).
698+
///
699+
/// If this is lower than the `amount_msats` passed to
700+
/// [`ChannelManager::pay_for_bolt11_invoice`] the call will fail with
701+
/// [`Bolt11PaymentError::InvalidAmount`].
702+
pub declared_total_mpp_value_override: Option<u64>,
689703
}
690704

691705
impl Default for OptionalBolt11PaymentParams {
@@ -697,6 +711,7 @@ impl Default for OptionalBolt11PaymentParams {
697711
retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)),
698712
#[cfg(not(feature = "std"))]
699713
retry_strategy: Retry::Attempts(3),
714+
declared_total_mpp_value_override: None,
700715
}
701716
}
702717
}
@@ -5455,10 +5470,18 @@ impl<
54555470
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
54565471
///
54575472
/// # Handling Invoice Amounts
5458-
/// Some invoices include a specific amount, while others require you to specify one.
5459-
/// - If the invoice **includes** an amount, user may provide an amount greater or equal to it
5460-
/// to allow for overpayments.
5461-
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
5473+
/// Some invoices require a specific amount (which can be fetched with
5474+
/// [`Bolt11Invoice::amount_milli_satoshis`]) while others allow you to pay amount amount.
5475+
///
5476+
/// - If the invoice **includes** an amount, `amount_msats` may be `None` to pay exactly
5477+
/// [`Bolt11Invoice::amount_milli_satoshis`] or may be `Some` with a value greater than or
5478+
/// equal to the [`Bolt11Invoice::amount_milli_satoshis`] to allow for deliberate overpayment
5479+
/// (e.g. for "tips").
5480+
/// - If the invoice **doesn't include** an amount, `amount_msats` must be `Some`.
5481+
///
5482+
/// In the special case that [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`]
5483+
/// is set, `amount_msats` may be `Some` and lower than
5484+
/// [`Bolt11Invoice::amount_milli_satoshis`]. See the parameter for more details.
54625485
///
54635486
/// If these conditions aren’t met, the function will return [`Bolt11PaymentError::InvalidAmount`].
54645487
///

lightning/src/ln/invoice_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ mod test {
690690
custom_tlvs: custom_tlvs.clone(),
691691
route_params_config: RouteParametersConfig::default(),
692692
retry_strategy: Retry::Attempts(0),
693+
declared_total_mpp_value_override: None,
693694
};
694695

695696
nodes[0]

lightning/src/ln/outbound_payment.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,11 @@ pub(crate) enum PaymentSendFailure {
627627
#[derive(Debug)]
628628
pub enum Bolt11PaymentError {
629629
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
630-
/// This happens when the user-provided amount is less than an amount specified in the [`Bolt11Invoice`].
630+
///
631+
/// This happens when the user-provided amount is less than an amount specified in the
632+
/// [`Bolt11Invoice`] or the amount set at
633+
/// [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`] was lower than the
634+
/// explicit amount provided to [`ChannelManager::pay_for_bolt11_invoice`].
631635
///
632636
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
633637
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
@@ -1031,9 +1035,11 @@ impl OutboundPayments {
10311035
{
10321036
let payment_hash = invoice.payment_hash();
10331037

1038+
let partial_payment = optional_params.declared_total_mpp_value_override.is_some();
10341039
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
10351040
(Some(amt), None) | (None, Some(amt)) => amt,
1036-
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt => return Err(Bolt11PaymentError::InvalidAmount),
1041+
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt && !partial_payment =>
1042+
return Err(Bolt11PaymentError::InvalidAmount),
10371043
(Some(_), Some(user_amt)) => user_amt,
10381044
(None, None) => return Err(Bolt11PaymentError::InvalidAmount),
10391045
};
@@ -1043,6 +1049,18 @@ impl OutboundPayments {
10431049
.with_custom_tlvs(optional_params.custom_tlvs);
10441050
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
10451051

1052+
if let Some(mpp_amt) = optional_params.declared_total_mpp_value_override {
1053+
if mpp_amt < amount {
1054+
return Err(Bolt11PaymentError::InvalidAmount);
1055+
}
1056+
if let Some(invoice_amount) = invoice.amount_milli_satoshis() {
1057+
if mpp_amt < invoice_amount {
1058+
return Err(Bolt11PaymentError::InvalidAmount);
1059+
}
1060+
}
1061+
recipient_onion.total_mpp_amount_msat = mpp_amt;
1062+
}
1063+
10461064
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
10471065
.with_user_config_ignoring_fee_limit(optional_params.route_params_config);
10481066

lightning/src/ln/payment_tests.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5442,3 +5442,160 @@ fn max_out_mpp_path() {
54425442
check_added_monitors(&nodes[0], 2); // one monitor update per MPP part
54435443
nodes[0].node.get_and_clear_pending_msg_events();
54445444
}
5445+
5446+
#[test]
5447+
fn bolt11_multi_node_mpp() {
5448+
// Test that multiple nodes can collaborate to pay a single BOLT 11 invoice, with each node
5449+
// paying a portion of the total invoice amount. This is useful for scenarios like:
5450+
// - Paying from multiple wallets (e.g., ecash wallets with funds in multiple mints)
5451+
// - Graduated wallets (funds split between trusted and self-custodial wallets)
5452+
5453+
let chanmon_cfgs = create_chanmon_cfgs(3);
5454+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
5455+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
5456+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
5457+
5458+
// Create channels: A<>C and B<>C
5459+
create_announced_chan_between_nodes(&nodes, 0, 2);
5460+
create_announced_chan_between_nodes(&nodes, 1, 2);
5461+
5462+
// Node C creates a BOLT 11 invoice for 100_000 msat
5463+
let invoice_amt_msat = 100_000;
5464+
let invoice_params = crate::ln::channelmanager::Bolt11InvoiceParameters {
5465+
amount_msats: Some(invoice_amt_msat),
5466+
..Default::default()
5467+
};
5468+
let invoice = nodes[2].node.create_bolt11_invoice(invoice_params).unwrap();
5469+
5470+
// Node A pays 60_000 msat (part of the total)
5471+
let node_a_payment_amt = 60_000;
5472+
let payment_id_a = PaymentId([1; 32]);
5473+
let optional_params_a = crate::ln::channelmanager::OptionalBolt11PaymentParams {
5474+
declared_total_mpp_value_override: Some(invoice_amt_msat),
5475+
..Default::default()
5476+
};
5477+
nodes[0]
5478+
.node
5479+
.pay_for_bolt11_invoice(&invoice, payment_id_a, Some(node_a_payment_amt), optional_params_a)
5480+
.unwrap();
5481+
check_added_monitors(&nodes[0], 1);
5482+
5483+
// Node B pays 40_000 msat (the remaining part)
5484+
let node_b_payment_amt = 40_000;
5485+
let payment_id_b = PaymentId([2; 32]);
5486+
let optional_params_b = crate::ln::channelmanager::OptionalBolt11PaymentParams {
5487+
declared_total_mpp_value_override: Some(invoice_amt_msat),
5488+
..Default::default()
5489+
};
5490+
nodes[1]
5491+
.node
5492+
.pay_for_bolt11_invoice(&invoice, payment_id_b, Some(node_b_payment_amt), optional_params_b)
5493+
.unwrap();
5494+
check_added_monitors(&nodes[1], 1);
5495+
5496+
let payment_event_a = SendEvent::from_node(&nodes[0]);
5497+
nodes[2].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event_a.msgs[0]);
5498+
do_commitment_signed_dance(&nodes[2], &nodes[0], &payment_event_a.commitment_msg, false, false);
5499+
5500+
let payment_event_b = SendEvent::from_node(&nodes[1]);
5501+
nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event_b.msgs[0]);
5502+
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_b.commitment_msg, false, false);
5503+
5504+
// Process the pending HTLCs on node C and generate the PaymentClaimable event
5505+
assert!(nodes[2].node.get_and_clear_pending_events().is_empty());
5506+
expect_and_process_pending_htlcs(&nodes[2], false);
5507+
let events = nodes[2].node.get_and_clear_pending_events();
5508+
assert_eq!(events.len(), 1);
5509+
let payment_preimage = match &events[0] {
5510+
Event::PaymentClaimable {
5511+
payment_hash,
5512+
amount_msat,
5513+
purpose: PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. },
5514+
..
5515+
} => {
5516+
assert_eq!(*payment_hash, invoice.payment_hash());
5517+
assert_eq!(*amount_msat, invoice_amt_msat);
5518+
payment_preimage.unwrap()
5519+
},
5520+
_ => panic!("Unexpected event: {:?}", events[0]),
5521+
};
5522+
5523+
nodes[2].node.claim_funds(payment_preimage);
5524+
5525+
expect_payment_claimed!(nodes[2], invoice.payment_hash(), invoice_amt_msat);
5526+
check_added_monitors(&nodes[2], 2);
5527+
5528+
// Get the fulfill messages from C to both A and B
5529+
let mut events_c = nodes[2].node.get_and_clear_pending_msg_events();
5530+
assert_eq!(events_c.len(), 2);
5531+
5532+
// Handle fulfill message from C to A
5533+
let fulfill_idx_a = events_c
5534+
.iter()
5535+
.position(|ev| {
5536+
if let MessageSendEvent::UpdateHTLCs { node_id, .. } = ev {
5537+
*node_id == nodes[0].node.get_our_node_id()
5538+
} else {
5539+
false
5540+
}
5541+
})
5542+
.unwrap();
5543+
let fulfill_idx_b = 1 - fulfill_idx_a;
5544+
5545+
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = events_c[fulfill_idx_a] {
5546+
nodes[0].node.handle_update_fulfill_htlc(
5547+
nodes[2].node.get_our_node_id(),
5548+
updates.update_fulfill_htlcs[0].clone(),
5549+
);
5550+
do_commitment_signed_dance(&nodes[0], &nodes[2], &updates.commitment_signed, false, false);
5551+
}
5552+
5553+
let payment_sent = nodes[0].node.get_and_clear_pending_events();
5554+
check_added_monitors(&nodes[0], 1);
5555+
5556+
assert_eq!(payment_sent.len(), 2, "{payment_sent:?}");
5557+
if let Event::PaymentSent { payment_id, payment_hash, amount_msat, fee_paid_msat, .. } =
5558+
&payment_sent[0]
5559+
{
5560+
assert_eq!(*payment_id, Some(payment_id_a));
5561+
assert_eq!(*payment_hash, invoice.payment_hash());
5562+
assert_eq!(*amount_msat, Some(node_a_payment_amt));
5563+
assert_eq!(*fee_paid_msat, Some(0));
5564+
} else {
5565+
panic!("{payment_sent:?}");
5566+
}
5567+
if let Event::PaymentPathSuccessful { payment_id, .. } = &payment_sent[1] {
5568+
assert_eq!(*payment_id, payment_id_a);
5569+
} else {
5570+
panic!("{payment_sent:?}");
5571+
}
5572+
5573+
// Handle fulfill message from C to B
5574+
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = events_c[fulfill_idx_b] {
5575+
nodes[1].node.handle_update_fulfill_htlc(
5576+
nodes[2].node.get_our_node_id(),
5577+
updates.update_fulfill_htlcs[0].clone(),
5578+
);
5579+
do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, false, false);
5580+
}
5581+
5582+
let payment_sent = nodes[1].node.get_and_clear_pending_events();
5583+
check_added_monitors(&nodes[1], 1);
5584+
5585+
assert_eq!(payment_sent.len(), 2, "{payment_sent:?}");
5586+
if let Event::PaymentSent { payment_id, payment_hash, amount_msat, fee_paid_msat, .. } =
5587+
&payment_sent[0]
5588+
{
5589+
assert_eq!(*payment_id, Some(payment_id_b));
5590+
assert_eq!(*payment_hash, invoice.payment_hash());
5591+
assert_eq!(*amount_msat, Some(node_b_payment_amt));
5592+
assert_eq!(*fee_paid_msat, Some(0));
5593+
} else {
5594+
panic!("{payment_sent:?}");
5595+
}
5596+
if let Event::PaymentPathSuccessful { payment_id, .. } = &payment_sent[1] {
5597+
assert_eq!(*payment_id, payment_id_b);
5598+
} else {
5599+
panic!("{payment_sent:?}");
5600+
}
5601+
}

0 commit comments

Comments
 (0)