Skip to content

Commit a4b00d4

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 2b48770 commit a4b00d4

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
@@ -689,6 +689,20 @@ pub struct OptionalBolt11PaymentParams {
689689
/// will ultimately fail once all pending paths have failed (generating an
690690
/// [`Event::PaymentFailed`]).
691691
pub retry_strategy: Retry,
692+
/// If the payment being made from this node is part of a larger MPP payment from multiple
693+
/// nodes (i.e. because a single payment is being made from multiple wallets), you can specify
694+
/// the total amount being paid here.
695+
///
696+
/// If this is set, it must be at least the [`Bolt11Invoice::amount_milli_satoshis`] for the
697+
/// invoice provided to [`ChannelManager::pay_for_bolt11_invoice`]. Further, if this is set,
698+
/// the `amount_msats` provided to [`ChannelManager::pay_for_bolt11_invoice`] is allowed to be
699+
/// lower than [`Bolt11Invoice::amount_milli_satoshis`] (as the payment we're making may be a
700+
/// small part of the amount needed to meet the invoice's minimum).
701+
///
702+
/// If this is lower than the `amount_msats` passed to
703+
/// [`ChannelManager::pay_for_bolt11_invoice`] the call will fail with
704+
/// [`Bolt11PaymentError::InvalidAmount`].
705+
pub declared_total_mpp_value_override: Option<u64>,
692706
}
693707

694708
impl Default for OptionalBolt11PaymentParams {
@@ -700,6 +714,7 @@ impl Default for OptionalBolt11PaymentParams {
700714
retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)),
701715
#[cfg(not(feature = "std"))]
702716
retry_strategy: Retry::Attempts(3),
717+
declared_total_mpp_value_override: None,
703718
}
704719
}
705720
}
@@ -5453,10 +5468,18 @@ impl<
54535468
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
54545469
///
54555470
/// # Handling Invoice Amounts
5456-
/// Some invoices include a specific amount, while others require you to specify one.
5457-
/// - If the invoice **includes** an amount, user may provide an amount greater or equal to it
5458-
/// to allow for overpayments.
5459-
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
5471+
/// Some invoices require a specific amount (which can be fetched with
5472+
/// [`Bolt11Invoice::amount_milli_satoshis`]) while others allow you to pay amount amount.
5473+
///
5474+
/// - If the invoice **includes** an amount, `amount_msats` may be `None` to pay exactly
5475+
/// [`Bolt11Invoice::amount_milli_satoshis`] or may be `Some` with a value greater than or
5476+
/// equal to the [`Bolt11Invoice::amount_milli_satoshis`] to allow for deliberate overpayment
5477+
/// (e.g. for "tips").
5478+
/// - If the invoice **doesn't include** an amount, `amount_msats` must be `Some`.
5479+
///
5480+
/// In the special case that [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`]
5481+
/// is set, `amount_msats` may be `Some` and lower than
5482+
/// [`Bolt11Invoice::amount_milli_satoshis`]. See the parameter for more details.
54605483
///
54615484
/// If these conditions aren’t met, the function will return [`Bolt11PaymentError::InvalidAmount`].
54625485
///

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
@@ -1081,9 +1085,11 @@ impl OutboundPayments {
10811085
{
10821086
let payment_hash = invoice.payment_hash();
10831087

1088+
let partial_payment = optional_params.declared_total_mpp_value_override.is_some();
10841089
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
10851090
(Some(amt), None) | (None, Some(amt)) => amt,
1086-
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt => return Err(Bolt11PaymentError::InvalidAmount),
1091+
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt && !partial_payment =>
1092+
return Err(Bolt11PaymentError::InvalidAmount),
10871093
(Some(_), Some(user_amt)) => user_amt,
10881094
(None, None) => return Err(Bolt11PaymentError::InvalidAmount),
10891095
};
@@ -1093,6 +1099,18 @@ impl OutboundPayments {
10931099
.with_custom_tlvs(optional_params.custom_tlvs);
10941100
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
10951101

1102+
if let Some(mpp_amt) = optional_params.declared_total_mpp_value_override {
1103+
if mpp_amt < amount {
1104+
return Err(Bolt11PaymentError::InvalidAmount);
1105+
}
1106+
if let Some(invoice_amount) = invoice.amount_milli_satoshis() {
1107+
if mpp_amt < invoice_amount {
1108+
return Err(Bolt11PaymentError::InvalidAmount);
1109+
}
1110+
}
1111+
recipient_onion.total_mpp_amount_msat = mpp_amt;
1112+
}
1113+
10961114
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
10971115
.with_user_config_ignoring_fee_limit(optional_params.route_params_config);
10981116

lightning/src/ln/payment_tests.rs

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

0 commit comments

Comments
 (0)